So I have an asynchronous function that I need to call a number of times. Naturally, I would like to use Promise.all
const promises = [];
ids.forEach((id) => {
stockoutPromises.push(
asyncFunc(id),
});
});
results[] = Promise.all(promises);
Okay no problem there but how do I know which id goes with which result? Sure I could iterate through both arrays again, but is there another way to do this? Is there a way that I can create a promise that when resolved gives me an object that contains both id and result?
Thanks!
Chain a .then onto the call of asyncFunc so that the resulting item is not just of the asyncFunc result, but of it and the ID in an object:
ids.forEach((id) => {
stockoutPromises.push(
asyncFunc(id).then(result => ({ id, result }))
);
});
But it'd be better to use .map instead of .push in a loop:
const results = await Promise.all(
ids.map(id =>
asyncFunc(id).then(result => ({ id, result }))
)
);
const firstItem = results[0];
console.log(firstItem.result, firstItem.id);
The result of Promise.all will be in the order that they were in originally (in the array of promises). So you can easily reassociate just using the index:
// zip takes an array of keys, and an array of values, and creates an object:
const zip = (a, b) => Object.fromEntries(a.map((k, i) => [k, b[i]]));
// first id will resolve last, second will resolve next, etc.
const ids = [0, 1, 2, 3, 4];
const promises = ids.map((i) => new Promise((resolve) => {
setTimeout(() => resolve(i), 700 - (100 * i));
}));
// but we still get the correct item in each spot, because of how
// Promise.all works:
(async () => {
console.log(zip(ids, await Promise.all(promises)));
})()
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all:
Returned values will be in order of the Promises passed, regardless of completion order.
Instead of using forEach, use map:
const promises = ids.map((id) => {
return asyncFunc(id)
});
const results = await Promise.all(promises);
map will return a new array of objects based on what the supplied function returns. In your case, you are calling an asyncFunc, which, I assume, returns a promise itself. So you can just return the results of map directlty without pushing to a new promises array.
Also, make sure to "await" the call to Promise.all;
You can check it out here in this jsfiddle that simply has the asyncFunc return a promise that doubles the id in the ids array.
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
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
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 am using map() over a long array and using fetch for each value. It will take many seconds to complete. I would like to know when the final push is complete and use the data in the array.
I tried Promise, Promise.all, Async/Await, but may have missed something obvious. I created this sample code to more simply illustrate the problem.
const arr = new Array(100).fill("todos")
const arrPush = []
const result = arr.map(data => {
fetch(`https://jsonplaceholder.typicode.com/${data}`)
.then(res => res.json())
.then(res => arrPush.push(res))
})
Promise.all(result).then(() => console.log(arrPush))
When the final value is added to array, do something. In this case console.log the complete array.
The function that you're passing to map doesn't have a return statement, so result is an array of undefineds. As a result, there's nothing for Promise.all to wait for.
Additionally, there's no need to manually push into an array. Once we add in the return statement, you'll have an array of promises, and Promise.all will resolve to an array with all the stuff you're currently trying to push.
So try this:
const arr = new Array(100).fill("todos")
const promises = arr.map(data => {
return fetch(`https://jsonplaceholder.typicode.com/${data}`)
.then(res => res.json());
});
Promise.all(promises)
.then(arrayOfResults => console.log(arrayOfResults));
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