I'm trying to figure out why promises seem to work differently inside of for...of vs. map().
data is an array of objects of the form {app: MY_APP, link: MY_LINK}. I'm trying to convert it to the form {app: MY_APP, status: true OR false}}. The function checkLink() is async because it uses node-fetch and basically returns whether the given link is 200. Using for...of, I get the desired object:
let objToSend = [];
for (obj of data) {
objToSend.push({ app: obj.app, status: await checkLink(obj.link) });
}
// returns [{app: MY_APP, status: true}, ...]
But using map, as follows, I get an array of pending promises instead. Any help would be appreciated:
let objToSend = data.map(async obj => {
return {
app: obj.app,
status: await checkLink(obj.link)
};
});
// returns [ Promise { <pending> }, ...]
I also tried doing Promise.all(objToSend) after the map code, but it instead returns Promise { <pending> }
I'll leave the explanation up to the other answers, but just want to point out that there is also an performance difference.
You first solution waits on every iteration for the promise to resolve. Only calling checkLink if the previous one has resolved. This is an sequential solution.
check link1 => wait => check link2 => wait => check link3 => wait
The second solution iterates over every elements and sends out requests, without waiting for promises to resolve (for this reason an array of promises is returned). If you wait for all promises to be resolved you find this solution is a lot faster, since the requests are send out in parallel.
check link1 => check link2 => check link3 => wait for all
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
async function checkLink(link) {
await sleep(Math.random() * 1000 + 500); // sleep between 500 and 1500 ms
console.log(link);
return `status #${link}`;
}
(async function () {
const data = new Array(5).fill().map((_, index) => ({ app: "app", link: index }));
let objToSend;
console.log("solution #1");
objToSend = [];
for (let obj of data) {
objToSend.push({ app: obj.app, status: await checkLink(obj.link) });
}
console.log(objToSend);
console.log("==============================================");
console.log("solution #2");
objToSend = await Promise.all(data.map(async obj => {
return {
app: obj.app,
status: await checkLink(obj.link)
};
}));
console.log(objToSend);
})();
In the snippet the first solution takes 500/1500 * 5 = 2500/7500 between 2500 and 7500 ms. While the second solution takes between 500 and 1500 ms (depending on the slowest value to resolve).
Async functions always returns Promises. In your map function, as the callback returns promises, an array of promises is being created.
To convert it to your format, use it with Promise.all():
(async()=>{
let objToSend = await Promise.all(data.map(async obj => {
return {
app: obj.app,
status: await checkLink(obj.link)
};
}));
console.log(objToSend) //[{app: MY_APP, status: true}, ...]
})()
The await always halts the execution of the async function it is in, it doesn't work differently in both cases. In the later example however, you do call some async functions, and those calls will evaluate to promises, whereas in your first example, all those awaits are in the same async function.
I also tried doing Promise.all(objToSend) after the map code, but it instead returns Promise { <pending> }
Yup, await that and you get an array of results.
Related
I'm new to javascript. as I know using array.map over async function returns array of promises.
and I can use await Promise.all to reolve all promise and it returns me data.
I want to understand how can I use asyn function inside array of object's key.
As I know using async function it never block as execution for unimportant line, ( here other execution is not dependent on url , so I'm trying to make it asynchronous)
async function showPost() {
const posts = [
{ title: 'a', url: ['q', 'o'] },
{ title: 'b', url: ['t', 'y'] },
];
const formattedPost = await formatPosts(posts);
console.log(formattedPost);
}
const formatPosts = async (posts) => {
const formattedPosts = await Promise.all( // NOTE: await promise.all on map
posts.map(async (post) => {
post.url = addUrl(post.url); //NOTE: here if I don' add await here post.url is Promise<Unknown>
return post;
})
);
return formattedPosts;
};
const addUrl = async (d) => {
const mm = await Promise.all(d.map((item) => item + '-dummyUrl'));
return mm;
};
showPost();
**CODE 1 without await , but inside await Promise.all **
CODE 2 with AWAIT on addURL call I get output
Why is it not resolving though it is inside await promise.all
thanks for help in advance
When you call Promise.all(), you're resolving the promises returned by the map() function. When you don't add await to addURL(), you're replacing that value in the object with a promise object, which isn't resolved by Promise.all().
Promises are still promises, even after they resolve.
On top of this, Array.prototype.map() isn't an async function, so addURL() doesn't need to be async, which makes adding await pointless.
I know using array.map over async function returns array of promises, and I can use await Promise.all to resolve all promise and it returns me data.
This will just wait for the promises that are elements of the array that was passed to Promise.all, not "all promises" anywhere.
Notice that without the await, you don't need to make the map callback an async function, and you don't need to wait for the promises in the mapped array then; your current code is exactly equivalent to
const formatPosts = async (posts) => {
const formattedPosts = posts.map((post) => {
post.url = addUrl(post.url);
return post;
});
return formattedPosts;
};
Why objects key still has unknown promise
Because you assigned a promise object to the object property. Never did you instruct anything to wait for that promise. This is what would be fixed by doing
post.url = await addUrl(post.url);
It seems I'm unable to use an async function as the first argument to Array.find(). I can't see why this code would not work what is happening under the hood?
function returnsPromise() {
return new Promise(resolve => resolve("done"));
}
async function findThing() {
const promiseReturn = await returnsPromise();
return promiseReturn;
}
async function run() {
const arr = [1, 2];
const found = await arr.find(async thing => {
const ret = await findThing();
console.log("runs once", thing);
return false;
});
console.log("doesn't wait");
}
run();
https://codesandbox.io/s/zk8ny3ol03
Simply put, find does not expect a promise to be returned, because it is not intended for asynchronous things. It loops through the array until one of the elements results in a truthy value being returned. An object, including a promise object, is truthy, and so the find stops on the first element.
If you want an asynchronous equivalent of find, you'll need to write it yourself. One consideration you'll want to have is whether you want to run things in parallel, or if you want to run them sequentially, blocking before you move on to the next index.
For example, here's a version that runs them all in parallel, and then once the promises are all resolved, it finds the first that yielded a truthy value.
async function findAsync(arr, asyncCallback) {
const promises = arr.map(asyncCallback);
const results = await Promise.all(promises);
const index = results.findIndex(result => result);
return arr[index];
}
//... to be used like:
findAsync(arr, async (thing) => {
const ret = await findThing();
return false;
})
Here is a TypeScript version that runs sequentially:
async function findAsyncSequential<T>(
array: T[],
predicate: (t: T) => Promise<boolean>,
): Promise<T | undefined> {
for (const t of array) {
if (await predicate(t)) {
return t;
}
}
return undefined;
}
It might help you to note that Array.prototype.filter is synchronous so it doesn't support async behaviour. I think that the same applies to the "find" property. You can always define your own async property :) Hope this helps!
The other answers provide two solutions to the problem:
Running the promises in parallel and awaiting all before returning the index
Running the promises sequencially, so awaiting every single promise before moving on to the next one.
Imagine you have five promises that finish at different times: The first after one second, the second after two seconds, etc... and the fifth after five seconds.
If I'm looking for the one that finished after three seconds:
The first solution will wait 5 seconds, until all promises are resolved. Then it looks for the one that matches.
The second one will evaluate the first three matches (1 + 2 + 3 = 6 seconds), before
returning.
Here's a third option that should usually be faster: Running the promises in parallel, but only waiting until the first match ("racing them"): 3 seconds.
function asyncFind(array, findFunction) {
return new Promise(resolve => {
let i = 0;
array.forEach(async item => {
if (await findFunction(await item)) {
resolve(item);
return;
}
i++;
if (array.length == i) {
resolve(undefined);
}
});
});
}
//can be used either when the array contains promises
var arr = [asyncFunction(), asyncFunction2()];
await asyncFind(arr, item => item == 3);
//or when the find function is async (or both)
var arr = [1, 2, 3];
await asyncFind(arr, async item => {
return await doSomething(item);
}
When looking for a non-existant item, solutions 1 and 3 will take the same amount of time (until all promises are evaluated, here 5 seconds). The sequencial approach (solution 2) would take 1+2+3+4+5 = 15 seconds.
Demo: https://jsfiddle.net/Bjoeni/w4ayh0bp
I came up with a solution that doesn't appear to be covered here, so I figured I'd share. I had the following requirements.
Accepts an async function (or a function that returns a promise)
Supplies item, index, and array to the function
Returns the item with the fastest resolved promise (all are evaluated in parallel)
Does not exit early if the async function rejects (throws an error)
Supports generic types (in TypeScript)
With those requirements, I came up with the following solution using Promise.any. It will resolve with the first fulfilled value or reject with an AggregateError if none of the promises are fulfilled.
type Predicate<T> = (item: T, index: number, items: T[]) => Promise<boolean>
export const any = async <T>(array: T[], predicate: Predicate<T>): Promise<T> => {
return Promise.any(
array.map(async (item, index, items) => {
if (await predicate(item, index, items)) {
return item
}
throw new Error()
})
)
}
Now we can search an array in parallel with async functions and return the fastest resolved result.
const things = [{ id: 0, ... }, { id: 1, ... }]
const found = await any(things, async (thing) => {
const otherThing = await getOtherThing()
return thing.id === otherThing.id
})
Enumerate the array sequentially
If we want to enumerate the array in sequence, as Array.find does, then we can do that with some modifications.
export const first = async <T>(array: T[], predicate: Predicate<T>): Promise<T> => {
for (const [index, item] of array.entries()) {
try {
if (await predicate(item, index, array)) {
return item
}
} catch {
// If we encounter an error, keep searching.
}
}
// If we do not find any matches, "reject" by raising an error.
throw new Error()
}
Now, we can search an array in sequence and return the first resolved result.
const things = [{ id: 0, ... }, { id: 1, ... }]
const found = await first(things, async (thing) => {
const otherThing = await getOtherThing()
return thing.id === otherThing.id
})
This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed 3 years ago.
In the code below, I expect mark-1 to fire before mark-2. I'm guessing that I'm not using await and async correctly some where.
I'm pretty sure that this line:
const things = await fs.promises.readdir(folder);
and this line
const stats = await fs.promises.stat(path);
are correct as we are awaiting the file system to respond.
I'm not conerned with error checking or cross-platform code as of yet, just getting the promises to work correctly
// Libraries
const fs = require('fs');
// API
getThings('_t/').then(() => {
console.log('mark-2')
})
// Accesses the file system and returns an array of files / folders
async function getThings (folder) {
const things = await fs.promises.readdir(folder);
things.forEach(async (thing)=>{
await getStats(thing, folder);
});
}
// Gets statistics for each file/folder
async function getStats (thing, folder) {
const path = folder + thing;
const stats = await fs.promises.stat(path);
console.log('mark-1');
}
The problem is that you are using async and await in the forEach call, and this doesn't work as you would expect.
The forEach method doesn't really care about the return value of the callback function (in this case the promise that getStats returns).
You should map the things array to promises, and use Promise.all:
async function getThings (folder) {
const things = await fs.promises.readdir(folder);
const promises = things.map(thing => getStats(thing, folder));
return await Promise.all(promises);
}
Note that this will execute the promises "in parallel", and not sequentially.
If you want to execute the promises secuentially, one by one, you can either, "reduce" the promise array, or use a conventional loop (for, for-of).
EDIT:
Let me try to clarify why using an async callback function with forEach doesn't work.
Attempt #1: De-sugarizing it:
In the original example we have something like this:
// ...
things.forEach(async (thing)=>{
await getStats(thing, folder);
});
// ...
If we separate the callback from the forEach, we have:
const callback = async (thing)=>{
await getStats(thing, folder);
};
things.forEach(callback);
If we "desugarize" the async function:
const callback = function (thing) {
return new Promise((resolve, reject) => {
try {
getStats(thing, folder).then(stat => /*do nothing*/);
} catch (err) {
reject(err);
}
resolve();
});
};
things.forEach(callback);
Marking a function with async ensures the function will return always return a promise, regardless its completion, if the function reaches its execution without a explicit return value, the promise will be resolved with undefined, if it returns some value, the promise will resolve to it, and finally if something within the function throws, the promise will be rejected.
As you can see the problem is that the promises are just not being awaited by anything, and also they are not resolving to any value. The await placed in the callback does actually nothing with the value, just as in the above I'm doing .then and doing nothing with the value.
Attempt 2: Implementing a simple forEach function:
function forEach(array, callback) {
for(const value of array) {
callback(value);
}
}
const values = [ 'a', 'b', 'c' ];
forEach(values, (item) => {
console.log(item)
});
The above forEach is an oversimplification of the Array.prototype.forEach method, just to show its structure, in reality the callback function is called passing the array as the this value, and passing three arguments, the current element, the current index, and again the array instance, but we get the idea.
If we would like to implement an async forEach function, we would have to await the callback call:
const sleep = (time, value) => new Promise(resolve => setTimeout(resolve(value), time));
const values = [ { time: 300, value: 'a'}, { time: 200, value: 'b' }, {time: 100, value: 'c' } ];
async function forEachAsync(array, callback) {
for(const value of array) {
await callback(value);
}
}
(async () => {
await forEachAsync(values, async (item) => {
console.log(await sleep(item.time, item.value))
});
console.log('done');
})()
The above forEachAsync function will iterate and await item by item, sequentially, normally you don't want that, if the async functions are independent, they can be done in parallel, just as I suggested in first place.
const sleep = (time, value) => new Promise(resolve => setTimeout(resolve(value), time));
const values = [ { time: 300, value: 'a'}, { time: 200, value: 'b' }, {time: 100, value: 'c' } ];
(async () => {
const promises = values.map(item => sleep(item.time, item.value));
const result = await Promise.all(promises);
console.log(result);
})()
And as you can see, even if the promises are executed in parallel, we get the results in the same order as the promises where in the array.
But the difference between this example and the first one is that this one takes only 300ms (the longest promise to resolve), and the first one takes 600ms (300ms + 200ms + 100ms).
Hope it makes it clearer.
I know that promise.all() fails when even 1 of the promise is failed. I only want to try for failed promises and don't want to run promise.all() again.
Any recommendations on how I can achieve this in minimal way?
Promises are eager construct and model a value obtained asynchronously,
a Promise is produced using some kind of producer, like fetch for instance.
If you retain a reference to this producer then you can replay the nmechanism
that produced the Promise in the first place.
// producer function
function getData (arg) {
const result = new Promise();
return result.then(value => {
return { ok:true, value };
}, error => {
return {
ok: false,
value: error,
// retry is a function which calls the producer with the same arguments
retry: () => getData(arg)
};
})
}
Then if you have something like:
const data = [];
// Promise<{ok: boolean, value: any, retry?: function}>
// No promises will fail in this array
const asyncResults = data.map(getResults);
Promise.all(asyncResults)
.then((results) => {
const successes = results.filter(res => res.ok);
const retrys = results.filter(res => !res.ok).map(res => res.retry()); // retry all failed promises
})
Memory leaks, stack overflow: because I retain a reference to original arguments in order to retry and the algorithm is recursive there could be a memory leak. However the algorithm cannot "stack overflow":
getData calls do not get "deeper" over time (see retry definition)
the asyncrhonicity of the algorithm prevent this behaviour if a promise was never resolved
old data is properly discarded when accessing the results as const resultData = results.filter(res => res.ok).map(res => res.value);
However the algorithm could take a long time to settle if a promise keep on not getting resolved and prevent access to the rest of the values.
In an alternative I suggest you take a look at another async primitive, not yet part of the language (maybe some day) : Observables which are designed for this kind of tasks: lazy, retry-able async operations.
You may use async package and wrap all promise calls with closure with done argument.
Then simply resolve results.
const async = require('async');
const promiseAll = promises => {
return new Promise((resolve) => {
// preparing array of functions which has done method as callback
const parallelCalls = promises.map(promise => {
return done => {
promise
.then(result => done(null, result)
.catch(error => {
console.error(error.message);
done();
});
}
});
// calling array of functions in parallel
async.parallel(
parallelCalls,
(_, results) => resolve(results.filter(Boolean))
);
});
};
router.get('/something', async (req, res) => {
...
const results = await promiseAll(promises);
...
});
Or we can simply do Promise.all without using async package:
router.get('/something', async (req, res) => {
...
const results = (
await Promise.all(
promises.map(promise => {
return new Promise(resolve => {
promise.then(resolve).catch(e => resolve());
});
});
)
).filter(Boolean);
...
});
I need to make several async calls that depend on each other. I initially wrote the code and used Promise.all to make async in steps. I looped through my data and created a async method in order to put all of the needed actions into an array to pass into Promise.all(). This works fine, but how could I do the same using Observables. I've read that forkJoin is the equivalent of Promise.all, but how could i loop through the data and wrap my async function, and then execute it before moving to the next flatMap?
public getMonthly(){
return this.http.get(url)
.flatMap(response => {
// need to convert this?
let actions = response.json().map(this.asyncMonthlyRecord);
return Promise.all(actions);
})
.flatMap(()=> this.queryMonthly())
.map(this.convertFromSQl)
.catch((error:any)=> Observable.throw(error || 'Server Error'));
}
private asyncMonthlyRecord = (record):Promise<any> => {
return this.setUsage(record,'HILowMonthly');
}
private queryMonthly(){
return this.storage.query('SELECT * FROM HILowMonthly')
}
getMonthly().subscribe(x => console.info(x)); // logs data from SQLite perfectly...
I think what you want is something like this
Rx.Observable.of({ itemIds: [1, 2, 3, 4, 5 ]})
.mergeMap(response => {
const promises = response.itemIds
.map(id => {
return new Promise((resolve, reject) => {
// Wait for some random time, then resolve the promise.
const timeout = Math.floor(Math.random() * 5000);
setTimeout(() => {
console.log(`Finished promise ${id}`); // debug only
resolve({id, resolved: true})
}, timeout);
});
});
// Wait until all Promises have been resolved, then return all
// of them in a single and ordered array.
return Rx.Observable.forkJoin(promises);
})
.subscribe(x => console.log(x));
Working code on jsbin
Notice that the promises resolve in an arbitrary order but are returned in the correct order.
The commented code in the jsbin example also shows each promise could be resolved individually and merged back into the original stream in case the order of the promises is not important.