How to supply metadata about the promises when calling Promise.all()? - javascript

I'm working on some Javascript code that looks like this:
const arrayOfPromises = getArrayOfPromises();
Promise.all(arrayOfPromises).then(array => doStuff(array));
I need to change getArrayOfPromises() to return some information about each promise - for simplicity, let's say it's a simple true/false flag for each. My first instinct was to refactor this function to return an array of objects instead, e.g:
const getArrayOfObjects = () => {
...
return [
{ promise: promise1, flag: flag1 },
{ promise: promise2, flag: flag2 },
...
{ promise: promisen, flag: flagn }
];
}
However, the above wouldn't work with Promise.all because it only works directly with an iterable of Promise objects without the wrapper objects in between.
What's the simplest way to supply such metadata for each of the promises and then work with it once all the promises have been resolved?

If you want to use this meta data once the promises have been resolved and you don't have any control over the values that are being resolved by the promises returned from getArrayOfPromises(), then you can simply call then() on every separate promise in the array and make it return the original value combined with your meta data. Though make sure you replace the original promise with the promise returned by the .then(). Array.prototype.map() makes this easy:
arrayOfPromises = arrayOfPromises.map(promise => promise.then(value => ({
value,
flag: true
})));
Example:
let arrayOfPromises = getArrayOfPromises();
arrayOfPromises = arrayOfPromises.map(promise => promise.then(value => ({
value,
flag: true
})));
Promise.all(arrayOfPromises).then(array => doStuff(array));
function getArrayOfPromises() {
return [sleep(1000), sleep(1500), sleep(2000)];
}
function doStuff(array) {
console.log(array);
}
function sleep(ms) {
return new Promise(resolve => setTimeout(() => resolve('resolved after ' + ms), ms));
}
If you do have control over what getArrayOfPromises() returns, then simply return the meta data as part of the value that is being resolved.

What's the simplest way to supply such metadata for each of the promises and then work with it once all the promises have been resolved?
Just put the data in each result value. It's not metadata about the promise, but about the value (?), and you don't need to work with it until all promises are resolve, so the results are the perfect place. The flags just become contents of the array that Promise.all(…) resolves to.

Related

Why objects key still has unknow promise , though I have used await for mapping

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);

async function returning an array of promises not the actual array of values

I have a function which will take am array of intergers, then it will call an other function which will take one interger and do some check and return a promise to resolve it.
I know that getDataById function is not relly doing async job, but keep in my this is just examples..
This is my code:
function getDataById(id) {
return new Promise((resolve, reject) => {
if (id > 0) resolve(id * 10);
if (id <= 0) reject("wrong input");
})
}
async function callService(idList) {
return await idList.map((id) => getDataById(id));
}
let teamIDList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, -1, -2, -3, -4];
callService(teamIDList).then(res => {
console.log(res);
});
I expect it will return an array of new numbers and some strings inside.
But it returned an array of promises.
await doesn't do anything special when you give it an array, it just wraps the array in a resolved promise.
To wait for the promises to settle, use Promise.all:
async function callService(idList) {
return await Promise.all(idList.map((id) => getDataById(id)));
}
Alternately:
function callService(idList) {
return Promise.all(idList.map((id) => getDataById(id)));
}
(Side note: No need for that arrow function:
return await Promise.all(idList.map(getDataById));
)
Note that both of those do things in this order:
Calls getDataById for each id
Waits for all of those promises to be fulfilled, or for one of them to fail
If they're all fulfilled, uses the array of results to resolve the callService's promise (remember, async functions always return promises)
If you wanted to wait for each call to getDataById to complete before doing the next call to getDataById, you'd do it with a simple loop:
// **IF** you wanted to wait for each `getDataById` call to finish
// before starting the next
async function callService(idList) {
const result = [];
for (const id of idList) {
result.push(await getDataById(id));
}
return result;
}
(You can also shoehorn that into a reduce if you want, but it doesn't buy you anything.)
But I think you want the first code block above, starting all the calls in parallel and waiting for them to finish.
You've said in a comment that your boss doesn't want you to use Promise.all because it rejects if any of the promises rejects. That's true, it does. If you don't want that, you may want to use allSettled instead. That's a Stage 4 proposal (e.g., actively being implemented in JavaScript engines) which is easily polyfilled.
async function callService(idList) {
return await Promise.allSettled(idList.map(getDataById));
}
Alternately:
function callService(idList) {
return Promise.allSettled(idList.map(getDataById));
}
The result is an array of objects where the fulfilled promises look like this:
{
status: "fulfilled",
value: (the value)
}
and the rejected ones look like this:
{
status: "fulfilled",
reason: (the rejection reason)
}
Obviously, if you want to transform that array, you can easily do so. For instance, if you wanted to return null for the ones you couldn't retrieve:
async function callService(idList) {
return (await Promise.allSettled(idList.map(getDataById)))
.map(entry => entry.status === "fulfilled" ? entry.value : null);
}
Or if you wanted to filter them out:
async function callService(idList) {
return (await Promise.allSettled(idList.map(getDataById)))
.map(entry => entry.status === "fulfilled" ? entry.value : null)
.filter(entry => entry !== null);
}
If you want to filter and map simultaneously, we can slightly abuse flatMap: :-)
async function callService(idList) {
return (await Promise.allSettled(idList.map(getDataById)))
.flatMap(entry => entry.status === "fulfilled" ? entry.value : []);
}

Async/await inside for...of vs. map

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.

async/await always returns promise

I'm trying async/await functionality. I have such code imitating a request:
const getJSON = async () => {
const request = () => new Promise((resolve, reject) => (
setTimeout(() => resolve({ foo: 'bar'}), 2000)
));
const json = await request();
return json;
}
When I use the code in this way
console.log(getJSON()); // returns Promise
it returns a Promise
but when I call this line of code
getJSON().then(json => console.log(json)); // prints { foo: 'bar' }
it prints json as expected
Is it possible to use just code like console.log(getJSON())? What don't I understand?
Every async function returns a Promise object. The await statement operates on a Promise, waiting until the Promise resolves or rejects.
So no, you can't do console.log on the result of an async function directly, even if you use await. Using await will make your function wait and then return a Promise which resolves immediately, but it won't unwrap the Promise for you. You still need to unwrap the Promise returned by the async function, either using await or using .then().
When you use .then() instead of console.logging directly, the .then() method makes the result of the Promise available to you. But you can't get the result of the Promise from outside the Promise. That's part of the model of working with Promises.
A function defined with async always returns a Promise. If you return any other value that is not a Promise, it will be implicitly wrapped in a Promise. The statement const json = await request(); unwraps the Promise returned by request() to a plain object { foo: 'bar' }. This is then wrapped in a Promise before being returned from getJSON so a Promise is what you ultimately get when you call getJSON(). So to unwrap it, you can either call getJSON().then() like you've done or do await getJSON() to get the resolved value.
Return value of an async function will always be an AsyncFunction Object, which will return a Promise when called. You can not change that return type. The point of async/await is to easily wait for other async process to complete inside an async function.
const getResOrErr = () => {
const callAsyncCodeHere = async () => {
const request = () =>
new Promise((resolve, reject) =>
setTimeout(() => resolve({ foo: "bar" }), 2000)
);
const json = await request();
return json;
};
return callAsyncCodeHere()
.then(console.log)
.catch(console.log);
};
getResOrErr();
Try this. You can achieve making a function inside your main function and then put you promise code inside that function. Call it there and when you get the response or error just return it.

Convert Promise.all into Observable

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.

Categories

Resources