The question is published on by Tutorial Guruji team.
I have a chain of several maps, one of which needs to perform a database operation for each array element, so I’m using async await.
const resultsAsPromises = arr .map(syncDouble) .map(asyncMapper)
This isn’t a problem if it is the last item in the chain because I can unwrap it with Promise.all
console.log('results', await Promise.all(resultsAsPromises))
However, there are other synchronous operations I need to perform afterward, so I’d like to have the promises’ values unwrapped before moving on to the next .map
.
Is there a way to do this? I thought perhaps just making an extraction mapper like
function extractPromiseValues(value) { return value.then(v => v); }
would work, but alas, no.
var arr = [1, 2, 3, 4, 5]; function timeout(i) { return new Promise((resolve) => { setTimeout(() => { return resolve(`number is ${i}`); }, 1); }); } function syncDouble(i) { return i * 2; } async function asyncMapper(i) { return await timeout(i) } function extractPromiseValues(value) { return value.then(v => v); } async function main() { const resultsAsPromises = arr .map(syncDouble) .map(asyncMapper) // .map(extractPromiseValues) console.log('results', await Promise.all(resultsAsPromises)) } main();
How can I unwrap an array of promises within a chain of array methods
Answer
Rather than passing an identity function to .then()
, pass your synchronous operation instead, OR await
the promise in an async
function before passing it to your synchronous operation:
function syncCapitalize(s) { return s.slice(0, 1).toUpperCase() + s.slice(1); } const resultsAsPromises = arr .map(syncDouble) .map(asyncMapper) .map(p => p.then(syncCapitalize)); // OR //.map(async p => syncCapitalize(await p));
In the context of your example, this would look like:
function timeout(i) { return new Promise(resolve => { setTimeout(() => { resolve(`number is ${i}`); }, 1); }); } function syncDouble(i) { return i * 2; } function asyncMapper(i) { return timeout(i); } function syncCapitalize(s) { return s.slice(0, 1).toUpperCase() + s.slice(1); } async function main() { const arr = [1, 2, 3, 4, 5]; const resultsAsPromises = arr .map(syncDouble) .map(asyncMapper) .map(p => p.then(syncCapitalize)); // OR //.map(async p => syncCapitalize(await p)); console.log('results', await Promise.all(resultsAsPromises)); } main();
Alternatively, if we are interpreting the question as Ghassen Louhaichi has, you could use the TC39 pipeline operator (|>
) proposal to write the chain using one of the options below:
F# Pipelines Proposal
const results = arr .map(syncDouble) .map(asyncMapper) |> Promise.all |> await .map(syncCapitalize);
Smart Pipelines Proposal
const results = (arr .map(syncDouble) .map(asyncMapper) |> await Promise.all(#)) .map(syncCapitalize);
Unfortunately, unless you are using a Babel plugin, or until one of these proposals is merged into the official ECMAScript specification, you have to wrap the chain with await Promise.all(...)
:
const results = (await Promise.all(arr .map(syncDouble) .map(asyncMapper))) .map(syncCapitalize);
Finally, in the context of your full example:
function timeout(i) { return new Promise(resolve => { setTimeout(() => { resolve(`number is ${i}`); }, 1); }); } function syncDouble(i) { return i * 2; } function asyncMapper(i) { return timeout(i); } function syncCapitalize(s) { return s.slice(0, 1).toUpperCase() + s.slice(1); } async function main() { const arr = [1, 2, 3, 4, 5]; const results = (await Promise.all(arr .map(syncDouble) .map(asyncMapper))) .map(syncCapitalize); console.log('results', results); } main();