Is it an anti-pattern to use async/await inside of a new Promise() constructor?
You're effectively using promises inside the promise constructor executor function, so this the Promise constructor anti-pattern.
Your code is a good example of the main risk: not propagating all errors safely. Read why there.
In addition, the use of async
/await
can make the same traps even more surprising. Compare:
let p = new Promise(resolve => {
""(); // TypeError
resolve();
});
(async () => {
await p;
})().catch(e => console.log("Caught: " + e)); // Catches it.
Why await statement is not working in new promise constructor
Because the function you're trying to use it in (the promise executor function you're passing the promise constructor) isn't an async
function (and should never be). The await
operator only exists in async
functions. The job of the promise executor is to start the asynchronous process that the promise will report the completion of. If doing that involves using an existing promise (which would be your reason for using an async
function, so you could await
it), you don't use new Promise
at all; you chain off the promise you already have. (More in the answers to Is it an anti-pattern to use async/await inside of a new Promise() constructor?. Spoiler: Yes, it is. :-D ) In your example, you'd just use the promise from timeout
, like this:
function timeout(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function test(num) {
await timeout(1000);
if (num == 11) {
throw "error"; // (Best practice is to use `new Error` here)
}
return "completed";
}
test(11).then((val) => { console.log(val) }).catch((err) => { console.log(err) });
Using await within a Promise
You do this:
async function outerFunction() {
const value = await somethingAsynchronous();
if (value === something) {
return 'It Worked!';
}
throw Error('Nope. Try again.');
}
Using async
wraps the result of outerFunction
with a Promise.
If you want that wrapping promise to resolve to something, just return it from the async
function. If you want the wrapping promise to be rejected, throw an error inside the async
function.
But then how do you account for code such as this?
async function outerFunction() {
if (someSynchronousCheck()) {
throw Error('Nope. Try again.');
}
const value = await somethingAsynchronous();
// ...
}
How can I use async/await in 'new Promise' block to fetch data from an api?
Never pass an async function
as the executor to new Promise
! If you want to use async
/await
and axios which already does return a promise, you don't need - and shouldn't use - the Promise
constructor to promisify a callback API. Just write
async function doesExist(query, retries = 0) {
const url = `http://api.example.com/json?fields=${query}`;
const maxRetries = 3;
const data = await axios.get(url);
if (data.statusCode === 200) {
return data;
} else if (retries < maxRetries) {
return doesExist(query, retries + 1);
} else {
throw new Error(`Could not get the data after ${retries} retries.`);
}
}
Is async code in a Promise always an antipattern?
The problem that the other question was warning about, and that could be a problem here, is that if the inside of the async callback passed to the Promise constructor awaits something that rejects, the Promise will hang instead of rejecting. Your current code will not result in f
ever rejecting, but setRepeatedTimeout
were to carry out a task which may reject, you'd get an unhandled rejection and permanent hanging:
var [setRepeatedTimeout, clearRepeatedTimeout] = (() => {
const asleep = (delay) => new Promise(resolve => setTimeout(resolve, delay));
const repeatedTimeoutIntervals = [];
function setRepeatedTimeout(f, delay, ...arguments) {
//Like setInterval, but waits for an invocation to complete before scheduling the next one
//(Supports both classic and async functions)
const mySemaphores = {notAborted: true};
const intervalID = repeatedTimeoutIntervals.push(mySemaphores) - 1;
new Promise( async (resolve) => {
await asleep(delay);
while(mySemaphores.notAborted) {
await f(...arguments);
await asleep(delay);
}
delete repeatedTimeoutIntervals[intervalID];
});
return intervalID;
}
function clearRepeatedTimeout(intervalID) {
//Clears loops set by setInterval()
repeatedTimeoutIntervals[intervalID].notAborted = false;
}
return [setRepeatedTimeout, clearRepeatedTimeout];
})();
const _ = { intervalID: undefined };
_.intervalID = setRepeatedTimeout(() => {
console.log('Throwing...');
return Promise.reject();
}, 2000)
can you await a function not defined as async but declared new Promise inside of it?
You need to actually return
the new Promise
, then you can await
it. If you don't, you just start a Promise and then immediately return undefined
, and you can't await undefined
.
const sqlUpsertRecommendations = recommendationsArray => {
// eslint-disable-next-line no-new
return new Promise(resolve => {
// ... rest of code
async
is really just syntactic sugar for creating and using Promise
s. An async
function just returns a new Promise
and that's what await
cares about, so await
will work if you have a normal function that returns a Promise
.
How to use async/await inside promise executor to read pdf content?
You don't need the promise constructor here. You would (although not quite in that way) if you had to use FileReader
, but you don't, Blob
has a text
method that reads the blob as text:
import axios from 'axios';
export const findNumberOfPages = async pdfUrl => {
try {
const response = await axios.get(pdfUrl, {
headers: { 'Content-Type': 'application/pdf' }
});
const result = await new Blob([response.data]).text();
const pages = result.match(/\/Type[\s]*\/Page[^s]/g);
const count = pages?.length;
return count;
} catch (error) {
throw new Error(`Error when fetching pdf: ${error.message}`);
}
};
I don't use axios, but I suspect that axios.get
call is a bit off. You've specified a Content-Type
, but you're not sending data (the Content-Type
request header specifies the type of data you're sending, not receiving). Also, I suspect you don't need the Blob
part of this at all.
Using the built-in fetch
instead, I'd expect you could do it like this:
import axios from 'axios';
export const findNumberOfPages = async pdfUrl => {
try {
const response = await fetch(pdfUrl);
if (!response.ok) {
throw new Error(`HTTP error ${response.status}`);
}
const result = await response.text();
const pages = result.match(/\/Type[\s]*\/Page[^s]/g);
const count = pages?.length;
return count;
} catch (error) {
throw new Error(`Error when fetching pdf: ${error.message}`);
}
};
What is the explicit promise construction antipattern and how do I avoid it?
The deferred antipattern (now explicit-construction anti-pattern) coined by Esailija is a common anti-pattern people who are new to promises make, I've made it myself when I first used promises. The problem with the above code is that is fails to utilize the fact that promises chain.
Promises can chain with .then
and you can return promises directly. Your code in getStuffDone
can be rewritten as:
function getStuffDone(param){
return myPromiseFn(param+1); // much nicer, right?
}
Promises are all about making asynchronous code more readable and behave like synchronous code without hiding that fact. Promises represent an abstraction over a value of one time operation, they abstract the notion of a statement or expression in a programming language.
You should only use deferred objects when you are converting an API to promises and can't do it automatically, or when you're writing aggregation functions that are easier expressed this way.
Quoting Esailija:
This is the most common anti-pattern. It is easy to fall into this when you don't really understand promises and think of them as glorified event emitters or callback utility. Let's recap: promises are about making asynchronous code retain most of the lost properties of synchronous code such as flat indentation and one exception channel.
JavaScript Promises confusion anti-pattern?
export const myExample = (payload) => {
return new Promise((resolve, reject) => {})
}
should only be used to convert code that is not promise based but returns the result asynchronously into a Promise.
export const myExample = (payload) => {
return new Promise(async (resolve, reject) => {})
}
Is an anty pattern async
already makes a function to return a Promise, and you break the promise chaining here.
export const myExample = async (payload) => {
return new Promise((resolve, reject) => {})
}
Same as the first one new Promise
should only be used to convert code that is not promise based but returns the result asynchronously into a Promise. If async
can be committed depends on the other code within that function. But it would be better that if you need to use new Promise((resolve, reject) => {})
that the enclosing function only contains and returns that new Promise
like in your first example.
also if that's the case, should I just return from function which will be same as resolve, and if I throw Error will be reject, so it would look like that ?
yes
Related Topics
Why Does a Regexp With Global Flag Give Wrong Results
What Is the 'New' Keyword in JavaScript
Convert a Unix Timestamp to Time in JavaScript
How to Do String Interpolation in JavaScript
When Should I Use Curly Braces For Es6 Import
Negative Lookbehind Equivalent in JavaScript
Difference Between Call and Apply
Generate Random Number Between Two Numbers in JavaScript
JavaScript: Client-Side Vs. Server-Side Validation
Parsing a String to a Date in JavaScript
Ways to Deal With #Document Under Iframe
Why Is Extending Native Objects a Bad Practice
Ecmascript 6 Arrow Function That Returns an Object
Download File Using JavaScript/Jquery
When Should I Use Arrow Functions in Ecmascript 6
Calling Functions With Settimeout()
Updating Address Bar With New Url Without Hash or Reloading the Page