Given an array of promises, what's the idiomatic way to get the results in ES7?
Here's what I want to do:
async function getImports() {
let imports = [System.import('./package1.js'), System.import('./package2.js')];
let promises = await* imports;
let results = [];
await promises.forEach(val => val.then(data => results.push(data))); //seems hacky
console.log(results); // array of 2 resolved imports
}
The result is correct, but I'm still doing a forEach and a then to turn the resolved promises into results. This just doesn't seem right to me. Is there a cleaner way?
As mentioned in the issue you filed, the core issue is that await* is no longer a thing and has been removed. Unfortunately, it was not properly throwing a syntax error in Babel 6 and was essentially being treated like a normal await.
You'll need to explicitly
let [p1, p2] = await Promise.all([
System.import('./package1.js'), System.import('./package2.js')]);
I cannot believe it actually works, forEach does return undefined which you cannot await. If you need a loop, use map to get an array of (promised) results.
In your case, you seem to be looking for a simple
async function getImports() {
let promises = [System.import('./package1.js'), System.import('./package2.js')];
let results = await Promise.all(promises)
console.log(results);
}
One way to do it this ....
async function abc() {
let p1 = getReviews();
let p2 = getMenu();
let [reviews, menu] = await results(p1, p2);
}
function results(...rest) {
return Promise.all(rest).catch(err => console.log(err));
}
Related
Given the following code:
var arr = [1,2,3,4,5];
var results: number[] = await arr.map(async (item): Promise<number> => {
await callAsynchronousOperation(item);
return item + 1;
});
which produces the following error:
TS2322: Type 'Promise<number>[]' is not assignable to type 'number[]'.
Type 'Promise<number> is not assignable to type 'number'.
How can I fix it? How can I make async await and Array.map work together?
The problem here is that you are trying to await an array of promises rather than a Promise. This doesn't do what you expect.
When the object passed to await is not a Promise, await simply returns the value as-is immediately instead of trying to resolve it. So since you passed await an array (of Promise objects) here instead of a Promise, the value returned by await is simply that array, which is of type Promise<number>[].
What you probably want to do is call Promise.all on the array returned by map in order to convert it to a single Promise before awaiting it.
According to the MDN docs for Promise.all:
The Promise.all(iterable) method returns a promise that resolves
when all of the promises in the iterable argument have resolved, or
rejects with the reason of the first passed promise that rejects.
So in your case:
var arr = [1, 2, 3, 4, 5];
var results: number[] = await Promise.all(arr.map(async (item): Promise<number> => {
await callAsynchronousOperation(item);
return item + 1;
}));
This will resolve the specific error you are encountering here.
Depending on exactly what it is you're trying to do you may also consider using Promise.allSettled, Promise.any, or Promise.race instead of Promise.all, though in most situations (almost certainly including this one) Promise.all will be the one you want.
Solution below to properly use async await and Array.map together. Process all elements of the array in parallel, asynchronously AND preserve the order:
const arr = [1, 2, 3, 4, 5, 6, 7, 8];
const randomDelay = () => new Promise(resolve => setTimeout(resolve, Math.random() * 1000));
const calc = async n => {
await randomDelay();
return n * 2;
};
const asyncFunc = async() => {
const unresolvedPromises = arr.map(calc);
const results = await Promise.all(unresolvedPromises);
document.write(results);
};
document.write('calculating...');
asyncFunc();
Also codepen.
Notice we only "await" for Promise.all. We call calc without "await" multiple times, and we collect an array of unresolved promises right away. Then Promise.all waits for resolution of all of them and returns an array with the resolved values in order.
This is simplest way to do it.
await Promise.all(
arr.map(async (element) => {
....
})
)
There's another solution for it if you are not using native Promises but Bluebird.
You could also try using Promise.map(), mixing the array.map and Promise.all
In you case:
var arr = [1,2,3,4,5];
var results: number[] = await Promise.map(arr, async (item): Promise<number> => {
await callAsynchronousOperation(item);
return item + 1;
});
If you map to an array of Promises, you can then resolve them all to an array of numbers. See Promise.all.
You can use:
for await (let resolvedPromise of arrayOfPromises) {
console.log(resolvedPromise)
}
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
If you wish to use Promise.all() instead you can go for Promise.allSettled()
So you can have better control over rejected promises.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
I'd recommend using Promise.all as mentioned above, but if you really feel like avoiding that approach, you can do a for or any other loop:
const arr = [1,2,3,4,5];
let resultingArr = [];
for (let i in arr){
await callAsynchronousOperation(i);
resultingArr.push(i + 1)
}
FYI: If you want to iterate over items of an array, rather than indices (#ralfoide 's comment), use of instead of in inside let i in arr statement.
A solution using modern-async's map():
import { map } from 'modern-async'
...
const result = await map(myArray, async (v) => {
...
})
The advantage of using that library is that you can control the concurrency using mapLimit() or mapSeries().
I had a task on BE side to find all entities from a repo, and to add a new property url and to return to controller layer. This is how I achieved it (thanks to Ajedi32's response):
async findAll(): Promise<ImageResponse[]> {
const images = await this.imageRepository.find(); // This is an array of type Image (DB entity)
const host = this.request.get('host');
const mappedImages = await Promise.all(images.map(image => ({...image, url: `http://${host}/images/${image.id}`}))); // This is an array of type Object
return plainToClass(ImageResponse, mappedImages); // Result is an array of type ImageResponse
}
Note: Image (entity) doesn't have property url, but ImageResponse - has
I'm struggling trying to have a javascript asynchrone sequence, although I have seen a lot of tutorials online.
Maybe you could help me :-)
I have an array of 6 functions (F1..F6)
Those function needs to be ran sequentially, forwarding a shared parameter from one function to the other.
And inside F3, I wanted to call another little function multiple times (currently using an array.map) ==> let's call them F3a, F3b, F3c
F3a, F3b, F3c needs to be sequential as well ; at least F4 must start AFTER all F3 are completed - and still those multilevel function are sharing the same global parameter.
I'm not sure I understand the full concept of Promises but I bet I need to learn more on them. :-)
The expected scheme I have in mind:
F1---|
******F2------|
***************F3a-|
********************F3b-|
*************************F3c-|
******************************F4------|
******************************F5------|
******************************F6------|
It might looks easy for all of you, but as a non developer, it's a bit harder for me to have the code working.
I have something partially working (using async and some Promises) and I guess (not sure) it is on the following sequencing; so not as expected :
F1---|
******F2------|
***************F3a-|
***************F3b-|
***************F3c-|
********************F4------|
********************F5------|
********************F6------|
I can't really show you the current code ; it's a total mess and I'd prefer to explain you the idea rather than getting the solution from you.
Should I use a nodejs library or should I code it using native js? Any library suggestion?
Any thought?
Thanks a lot.
You can do something like this without any external library
const sharedVariable = {}
async function doTask(data) {
const data1 = await f1(data, sharedVariable);
const data2 = await f2(data1, sharedVariable);
const data3 = await f3(data2, sharedVariable);
const data4 = await f4(data3, sharedVariable);
const data5 = await f5(data4, sharedVariable);
const data6 = await f6(data5, sharedVariable);
return data6;
}
async function f3(data) {
const data1 = await f3a(data, sharedVariable);
const data2 = await f3b(data1, sharedVariable);
const data3 = await f3c(data2, sharedVariable);
return data3;
}
doTask();
I'm not sure I understand the full concept of Promises but I bet I need to learn more on them. :-)
Yes, go ahead! What you describe is really easy to do using promises with async/await syntax:
async function f3(arg) {
await f3a();
await f3b();
await f3c();
return res;
}
async function main(val) {
await f6(await f5(await f4(await f3(await f2(await f1(val))))));
}
You can also easily use an actual loop if you have the functions in an array:
async function main(val) {
for (const f of fs)
val = await f(val);
}
d3-queue does a pretty good job for me. I also used it to create multiple nested queues. The API's easy to understand.
in your case it can look like this:
// all functions f1-f6 should have the same signarure
// function fN(sharedPArameter, done) ...
// done - is a callback you should call when done
function F3(sharedParameter, done) {
let nestedQ = d3.queue(1);
nestedQ.defer(f3a, sharedParameter);
nestedQ.defer(f3b, sharedParameter);
nestedQ.defer(f3c, sharedParameter);
nestedQ.awaitAll(error => {
done(error);
});
}
let q = d3.queue(1); // put 1 for sequential execution
q.defer(f1, sharedParameter);
q.defer(f2, sharedParameter);
q.defer(f3, sharedParameter);
q.defer(f4, sharedParameter);
q.awaitAll((error, result) => {
// when everything's done
});
Given the following code:
var arr = [1,2,3,4,5];
var results: number[] = await arr.map(async (item): Promise<number> => {
await callAsynchronousOperation(item);
return item + 1;
});
which produces the following error:
TS2322: Type 'Promise<number>[]' is not assignable to type 'number[]'.
Type 'Promise<number> is not assignable to type 'number'.
How can I fix it? How can I make async await and Array.map work together?
The problem here is that you are trying to await an array of promises rather than a Promise. This doesn't do what you expect.
When the object passed to await is not a Promise, await simply returns the value as-is immediately instead of trying to resolve it. So since you passed await an array (of Promise objects) here instead of a Promise, the value returned by await is simply that array, which is of type Promise<number>[].
What you probably want to do is call Promise.all on the array returned by map in order to convert it to a single Promise before awaiting it.
According to the MDN docs for Promise.all:
The Promise.all(iterable) method returns a promise that resolves
when all of the promises in the iterable argument have resolved, or
rejects with the reason of the first passed promise that rejects.
So in your case:
var arr = [1, 2, 3, 4, 5];
var results: number[] = await Promise.all(arr.map(async (item): Promise<number> => {
await callAsynchronousOperation(item);
return item + 1;
}));
This will resolve the specific error you are encountering here.
Depending on exactly what it is you're trying to do you may also consider using Promise.allSettled, Promise.any, or Promise.race instead of Promise.all, though in most situations (almost certainly including this one) Promise.all will be the one you want.
Solution below to properly use async await and Array.map together. Process all elements of the array in parallel, asynchronously AND preserve the order:
const arr = [1, 2, 3, 4, 5, 6, 7, 8];
const randomDelay = () => new Promise(resolve => setTimeout(resolve, Math.random() * 1000));
const calc = async n => {
await randomDelay();
return n * 2;
};
const asyncFunc = async() => {
const unresolvedPromises = arr.map(calc);
const results = await Promise.all(unresolvedPromises);
document.write(results);
};
document.write('calculating...');
asyncFunc();
Also codepen.
Notice we only "await" for Promise.all. We call calc without "await" multiple times, and we collect an array of unresolved promises right away. Then Promise.all waits for resolution of all of them and returns an array with the resolved values in order.
This is simplest way to do it.
await Promise.all(
arr.map(async (element) => {
....
})
)
There's another solution for it if you are not using native Promises but Bluebird.
You could also try using Promise.map(), mixing the array.map and Promise.all
In you case:
var arr = [1,2,3,4,5];
var results: number[] = await Promise.map(arr, async (item): Promise<number> => {
await callAsynchronousOperation(item);
return item + 1;
});
If you map to an array of Promises, you can then resolve them all to an array of numbers. See Promise.all.
You can use:
for await (let resolvedPromise of arrayOfPromises) {
console.log(resolvedPromise)
}
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
If you wish to use Promise.all() instead you can go for Promise.allSettled()
So you can have better control over rejected promises.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
I'd recommend using Promise.all as mentioned above, but if you really feel like avoiding that approach, you can do a for or any other loop:
const arr = [1,2,3,4,5];
let resultingArr = [];
for (let i in arr){
await callAsynchronousOperation(i);
resultingArr.push(i + 1)
}
FYI: If you want to iterate over items of an array, rather than indices (#ralfoide 's comment), use of instead of in inside let i in arr statement.
A solution using modern-async's map():
import { map } from 'modern-async'
...
const result = await map(myArray, async (v) => {
...
})
The advantage of using that library is that you can control the concurrency using mapLimit() or mapSeries().
I had a task on BE side to find all entities from a repo, and to add a new property url and to return to controller layer. This is how I achieved it (thanks to Ajedi32's response):
async findAll(): Promise<ImageResponse[]> {
const images = await this.imageRepository.find(); // This is an array of type Image (DB entity)
const host = this.request.get('host');
const mappedImages = await Promise.all(images.map(image => ({...image, url: `http://${host}/images/${image.id}`}))); // This is an array of type Object
return plainToClass(ImageResponse, mappedImages); // Result is an array of type ImageResponse
}
Note: Image (entity) doesn't have property url, but ImageResponse - has
I have an Object that name is uploadedFiles. when I run this code first run console.log then run the for so I get the empty array. how can I solve the problem
let orderFilesData = [];
for (let key in uploadedFiles) {
uploadedFiles[key].map(async (file) => {
let id = file.id;
const orderFile = await this.orderFileRepository.findOne(id);
orderFile.order = order;
await this.orderFileRepository.save(orderFile);
orderFilesData.push(orderFile.fileUrl);
});
}
console.log(orderFilesData);
Since you do not return any data from the map, try using a foreach loop. Since you use an async function, what you set in orderFilesData will be an array of promises, and you'll have to await them. The simplest solution is to use Promise.all the array (console.log(Promise.all(orderFilesData)) should do what you want)
when array.map is used with async function it returns back a list of promises that is not runned. You'll have to start the process with Promise.all (or other).
Try this inside your for loop
const uploadPromises = uploadedFiles[key].map(async (file) => {
...
});
await Promise.all(uploadPromises)
I suspect that the problem is that Array.map is async, so even though each one of the calls to save has await in front of it, iterating the elements and calling the anonymous function inside the .map is done in an async manner.
Try replacing uploadedFiles[key].map with a simple for loop and I believe that it'll fix the issue.
uploadedFiles seem to be an object, where the values of the keys are arrays? So if you call uploadedFiles[key].map(...) you are creating an array of promises where each of them seems to be awaited. But as the callback of map is asynchronous, you are in fact not awaiting. The simplest would be using Promise.all() to await all promises in the array of promises resulting from map.
let orderFilesData = [];
for (let key in uploadedFiles) {
await Promise.all(uploadedFiles[key].map(async (file) => {
let id = file.id;
const orderFile = await this.orderFileRepository.findOne(id);
orderFile.order = order;
await this.orderFileRepository.save(orderFile);
orderFilesData.push(orderFile.fileUrl);
}));
}
console.log(orderFilesData);
But for this to work, make sure the surrounding function is async
By default the Promise.All([]) function returns a number based index array that contains the results of each promise.
var promises = [];
promises.push(myFuncAsync1()); //returns 1
promises.push(myFuncAsync1()); //returns 2
Promise.all(promises).then((results)=>{
//results = [0,1]
}
What is the best vanilla way to return a named index of results with Promise.all()?
I tried with a Map, but it returns results in an array this way:
[key1, value1, key2, value2]
UPDATE:
My questions seems unclear, here is why i don't like ordered based index:
it's crappy to maintain: if you add a promise in your code you may have to rewrite the whole results function because the index may have change.
it's awful to read: results[42] (can be fixed with jib's answer below)
Not really usable in a dynamic context:
var promises = [];
if(...)
promises.push(...);
else{
[...].forEach(... => {
if(...)
promises.push(...);
else
[...].forEach(... => {
promises.push(...);
});
});
}
Promise.all(promises).then((resultsArr)=>{
/*Here i am basically fucked without clear named results
that dont rely on promises' ordering in the array */
});
ES6 supports destructuring, so if you just want to name the results you can write:
var myFuncAsync1 = () => Promise.resolve(1);
var myFuncAsync2 = () => Promise.resolve(2);
Promise.all([myFuncAsync1(), myFuncAsync2()])
.then(([result1, result2]) => console.log(result1 +" and "+ result2)) //1 and 2
.catch(e => console.error(e));
Works in Firefox and Chrome now.
Is this the kind of thing?
var promises = [];
promises.push(myFuncAsync1().then(r => ({name : "func1", result : r})));
promises.push(myFuncAsync1().then(r => ({name : "func2", result : r})));
Promise.all(promises).then(results => {
var lookup = results.reduce((prev, curr) => {
prev[curr.name] = curr.result;
return prev;
}, {});
var firstResult = lookup["func1"];
var secondResult = lookup["func2"];
}
If you don't want to modify the format of result objects, here is a helper function that allows assigning a name to each entry to access it later.
const allNamed = (nameToPromise) => {
const entries = Object.entries(nameToPromise);
return Promise.all(entries.map(e => e[1]))
.then(results => {
const nameToResult = {};
for (let i = 0; i < results.length; ++i) {
const name = entries[i][0];
nameToResult[name] = results[i];
}
return nameToResult;
});
};
Usage:
var lookup = await allNamed({
rootStatus: fetch('https://stackoverflow.com/').then(rs => rs.status),
badRouteStatus: fetch('https://stackoverflow.com/badRoute').then(rs => rs.status),
});
var firstResult = lookup.rootStatus; // = 200
var secondResult = lookup.badRouteStatus; // = 404
If you are using typescript you can even specify relationship between input keys and results using keyof construct:
type ThenArg<T> = T extends PromiseLike<infer U> ? U : T;
export const allNamed = <
T extends Record<string, Promise<any>>,
TResolved extends {[P in keyof T]: ThenArg<T[P]>}
>(nameToPromise: T): Promise<TResolved> => {
const entries = Object.entries(nameToPromise);
return Promise.all(entries.map(e => e[1]))
.then(results => {
const nameToResult: TResolved = <any>{};
for (let i = 0; i < results.length; ++i) {
const name: keyof T = entries[i][0];
nameToResult[name] = results[i];
}
return nameToResult;
});
};
A great solution for this is to use async await. Not exactly ES6 like you asked, but ES8! But since Babel supports it fully, here we go:
You can avoid using only the array index by using async/await as follows.
This async function allows you to literally halt your code inside of it by allowing you to use the await keyword inside of the function, placing it before a promise. As as an async function encounters await on a promise that hasn't yet been resolved, the function immediately returns a pending promise. This returned promise resolves as soon as the function actually finishes later on. The function will only resume when the previously awaited promise is resolved, during which it will resolve the entire await Promise statement to the return value of that Promise, allowing you to put it inside of a variable. This effectively allows you to halt your code without blocking the thread. It's a great way to handle asynchronous stuff in JavaScript in general, because it makes your code more chronological and therefore easier to reason about:
async function resolvePromiseObject(promiseObject) {
await Promise.all(Object.values(promiseObject));
const ret = {};
for ([key, value] of Object.entries(promiseObject)) {
// All these resolve instantly due to the previous await
ret[key] = await value;
};
return ret;
}
As with anything above ES5: Please make sure that Babel is configured correctly so that users on older browsers can run your code without issue. You can make async await work flawlessly on even IE11, as long as your babel configuration is right.
in regards to #kragovip's answer, the reason you want to avoid that is shown here:
https://medium.com/front-end-weekly/async-await-is-not-about-making-asynchronous-code-synchronous-ba5937a0c11e
"...it’s really easy to get used to await all of your network and I/O calls.
However, you should be careful when using it multiple times in a row as the await keyword stops execution of all the code after it. (Exactly as it would be in synchronous code)"
Bad Example (DONT FOLLOW)
async function processData() {
const data1 = await downloadFromService1();
const data2 = await downloadFromService2();
const data3 = await downloadFromService3();
...
}
"There is also absolutely no need to wait for the completion of first request as none of other requests depend on its result.
We would like to have requests sent in parallel and wait for all of them to finish simultaneously. This is where the power of asynchronous event-driven programming lies.
To fix this we can use Promise.all() method. We save Promises from async function calls to variables, combine them to an array and await them all at once."
Instead
async function processData() {
const promise1 = downloadFromService1();
const promise2 = downloadFromService2();
const promise3 = downloadFromService3();
const allResults = await Promise.all([promise1, promise2, promise3]);