This question already has an answer here:
Why does property access with await appear to result in `undefined` when the object is returned from an async function? [duplicate]
(1 answer)
Closed 25 days ago.
I just try to understand what is happening - why my async method is waiting for another async method only if the response is deconstructed?
So I have some example code:
Dummy promise
const psedoRequest = () => {
return new Promise(resolve => setTimeout(resolve, 2000, "resolved"));
}
Dummy method which is calling promise
const methodRequest = async() => {
let response = "";
let error = "";
try {
response = await psedoRequest();
} catch (e) {
error = e;
}
return { response, error };
}
Actual methods
const invalidMainMethod = async() => {
const results = await methodRequest().response;
console.log('Invalid', results)
// the same would be with:
// const response = await methodRequest().response;
// console.log('Invalid', response );
}
const validMainMethod = async() => {
let results = ""
const { response } = await methodRequest();
results = response;
console.log('Valid', results);
}
Console log returns:
Invalid undefined
Valid resolved
Why deconstructing actually works in that case - so it's waiting for a response while accessing directly .response is not?
I thought that deconstructing is some syntactic sugar.
const results = await methodRequest().response;
Property accessing has higher precedence than await so this:
Calls methodRequest
Gets a promise object
Reads response (which is undefined) from the promise
awaits undefined (which isn't a promise so has no significant effect)
Assigns undefined to results
Later the promise resolves.
You could get the desired effect by using parentheses to override precedence:
const results = (await methodRequest()).response;
const { response } = await methodRequest();
await has higher precedence than = so it awaits the promise first then assigns and deconstruction is done as part of assignment.
This is ambiguous (to humans) and doesn't mean what you think it means:
await methodRequest().response
Are you awaiting methodRequest()? Or are you awaiting the response property from what methodRequest() returns?
Be explicit:
const results = (await methodRequest()).response;
I see David was faster, but since I have already written my response here it is anyways:
The simple difference here is the syntax:
const response = await methodRequest().response;
This snippet awaits methodRequest().response
What you are expecting it to do is to await methodRequest() and then get the response.
const { response } = await methodRequest();
This awaits methodRequest() and then takes the response from it like this:
const response = (await methodRequest()).response
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 want to use Eslint plugin in my project with Webpack but it does not let me use await inside the loop.
According to Eslint docs it recommends to remove await from the loop and just add a Promise after.
Wrong example:
async function foo(things) {
const results = [];
for (const thing of things) {
// Bad: each loop iteration is delayed until the entire asynchronous operation completes
results.push(await bar(thing));
}
return baz(results);
}
Correct example:
async function foo(things) {
const results = [];
for (const thing of things) {
// Good: all asynchronous operations are immediately started.
results.push(bar(thing));
}
// Now that all the asynchronous operations are running, here we wait until they all complete.
return baz(await Promise.all(results));
}
But in my code I just merge data into one array which comes from HTTP request:
async update() {
let array = [];
for (url of this.myUrls) {
const response = await this.getData(url);
array = await array.concat(response);
}
}
Is it possible to remove await from this loop and add Promise just for array concatenation? I don't have an idea how to do it...
If you like one-liners.
const array = await Promise.all(this.myUrls.map((url)=> this.getData(url)));
In this case, the map method returns a bunch of promises, based on the URL, and your getData method. The Promise.all waits until all of your promises will be resolved. These promises run parallel.
You can use promise like this:
function update() {
let array = [],req=[];
for (url of this.myUrls) {
req.push(this.getData(url));
}
return Promise.all(req).then((data)=>{
console.log(data);
return data;
})
}
If I'm understanding you correctly, your getData function returns an array?
Using the one-liner supplied by Anarno, we can wait until all promises are resolved and then concatenate all the arrays.
const allResults = await Promise.all(this.myUrls.map((url) => this.getData(url)));
let finalArray = [];
allResults.forEach((item) => finalArray = finalArray.concat(item));
I have an async function:
const _getSelectedComponentVariantByComponentName = async (name) => {
const response = await api.get(`/internal/api/Component/GetComponentVariantByComponentName/${name}`);
componentRow.component = response.data;
return componentRow;
};
And I'm trying to use this function inside .map() method:
let componentRows = [...getState().formulaBuilder.componentRows];
componentRows = componentRows.map(async row => {
row = await _getSelectedComponentVariantByComponentName(row.name);
return row;
});
But in this case I got a Promise with status "pending".
How to wait for completion of async api call and return a value;
You can make use of Promise.all with map and use await with it.
map will return an array of Promises, Promise.all will wait for all promises to resolve and then resolve with the values as an array for each promise.
Also make sure you execute the below code in an async function:
let componentRows = [...getState().formulaBuilder.componentRows];
componentRows = await Promise.all(componentRows.map(row => {
return _getSelectedComponentVariantByComponentName(row.name);
}));
First of all, there are some issues with console.log in Google Chrome not functioning as expected. This is not the case as I am working in VSCode.
We begin with two async calls to the server.
promise_a = fetch(url)
promise_b = fetch(url)
Since fetch results are also promises, .json() will needed to be called on each item. The helper function process will be used, as suggested by a Stackoverflow user -- sorry lost the link.
let promiseResults = []
let process = prom => {
prom.then(data => {
promiseResults.push(data);
});
};
Promise.all is called. The resulting array is passed to .then where forEach calls process on item.json() each iteration and fulfilled promises are pushed to promiseResults.
Promise.all([promise_a, promise_b])
.then(responseArr => {
responseArr.forEach(item => {
process(item.json());
});
})
No argument is given to the final .then block because promiseResults are in the outer scope. console.log show confusing results.
.then(() => {
console.log(promiseResults); // correct results
console.log(promiseResults[0]); // undefined ?!?
})
Any help will be greatly appreciated.
If you are familiar with async/await syntax, I would suggest you not to use an external variable promiseResults, but return the results on the fly with this function:
async function getJsonResults(promisesArr) {
// Get fetch promises response
const results = await Promise.all(promisesArr);
// Get JSON from each response promise
const jsonResults = await Promise.all(results.map(r => r.json()));
return jsonResults
}
This is usage example:
promise_a = fetch(url1)
promise_b = fetch(url2)
getJsonResults([promise_a, promise_b])
.then(theResults => console.log('All results:', theResults))
Use theResults variable to extract necessary results.
You can try this, it looks the array loop is not going properly in the promise env.
Specifically: the promiseResults is filled after you are logging.
var resultAll = Promise.all([promise_a, promise_b])
.then(responseArr => {
return Promise.all(responseArr.map(item => return item.json()));
});
resultAll.then(promiseResults => {
console.log(promiseResults);
});