Is It an Anti-Pattern to Use Async/Await Inside of a New Promise() Constructor

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 Promises. 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



Leave a reply



Submit