I'm building express middleware to make two async calls to the database to check if the username or email are already in use. The functions return promises without a catch, as I want to keep database logic seperate from req/res/next logic, and I have centralised error handling that requires next as an argument. In my postman testing on local environment the following code works as expected and my centralised errorhandler returns the error to the client:
async checkUsernameExists(username) {
await this.sequelize.transaction(
async () =>
await this.User.findOne({
where: {
username,
},
}).then(user => {
if (user) throw new Conflict('Failed. Username already in use.');
}),
);
}
const checkDuplicateUsernameOrEmail = async (
{ body: { email, username } },
res,
next,
) => {
await Promise.all([
checkUsernameExists(username),
checkEmailExists(email),
])
.then(() => next())
.catch(error => next(error));
};
However, as the checkExists functions are async, shouldn't they be included in Promise.all with await? Or does Promise.all do this automatically?
await Promise.all([
await checkUsernameExists(username),
await checkEmailExists(email),
])...
This leads to an unhandled promise rejection from checkUsernameExists and no response is sent back to the client.
Should I use await inside Promise.all?
No (at least not the way you're doing it). Promise.all accepts and expects an array of Promises. Once they all resolve, or if one rejects, the Promise.all will resolve or reject. If you use await, you'll be passing an array of plain non-Promise values to Promise.all, which isn't the logic you want. If you use await, you'll also be waiting for the Promises to resolve serially, rather than in parallel, defeating the entire point of Promise.all. For example:
await Promise.all([
await checkUsernameExists(username),
await checkEmailExists(email),
])...
If checkUsernameExists takes 0.5 seconds to resolve, and checkEmailExists also takes 0.5 seconds to resolve, it will take at least 1 second for the Promise.all to resolve, because the Promises are being resolved via the await checkUsernameExistss, not by the Promise.all itself.
You should definitely do
await Promise.all([
checkUsernameExists(username),
checkEmailExists(email),
])
Async functions return Promises - to the Promise.all, someFnThatReturnsAPromise() is the same as somePromise. So there's absolutely nothing wrong with invoking the function and putting the resulting Promise in the array to pass to Promise.all.
Related
I have an array of fetch API calls that have been resolved as shown below:
let results = await Promise.all(promises);
I'm trying to get the result of each resolved fetch API call in the array. I tried console logging the results expecting the resolved responses
const newStuff = await results.map(async (response) => {
try {
const recipeDetail = await response.json();
return recipeDetail;
} catch (err) {
console.log("error1: ", err);
}
})
console.log(newStuff);
But I get back an array of pending promises.
However this below seems to work.
const newStuff = await Promise.all(results.map(async response => {
let recipeDetail = await response.json();
return recipeDetail;
}));
console.log(newStuff);
Why does the second example work when the first one doesn't?
Thanks in advance.
Remember that an async function always returns a promise.
In your version that doesn't work, you're awaiting the result of map, but there's no reason to do that, because map returns an array, not a promise. The array contains the promises from the calls to the async callback you provided map, but they're still pending since nothing has waited for them to settle (map knows nothing about promises). Your code within the async callback waits for the promises from fetch to settle before settling the async callback's promise, but nothing waits for the promise from the async callbacks to setting.
That's the purpose of Promise.all: It waits for the promises to settle and fulfills its promise with an array of the fulfillment values (not an array of promises).
If I have promise running synchronously, how do I want for both of them to finish? Doesn't await only work for a single Promise?
async function testing() {
let promise1 = new Promise((resolve, reject) => {
setTimeout(() => resolve("one"), 1000)
});
let promise2 = new Promise((resolve, reject) => {
setTimeout(() => resolve("two"), 1000)
});
// how do I await both of these?
}
You can use Promise.all to wait for both of them at once
const [result1, result2] = await Promise.all([promise1, promise2]))
If you want to resolve all promises then you can do two things which are Promise.allSettled() or Promise.all(). So based on your need, choose one.
The Promise.allSettled() method returns a promise that resolves after all of the given promises have either fulfilled or rejected, with an array of objects that each describes the outcome of each promise.
Promise.allSettled([
Promise.resolve('promise1'),
Promise.reject('promise2')
]).then(console.log)
It is typically used when you have multiple asynchronous tasks that are not dependent on one another to complete successfully, or you'd always like to know the result of each promise.
In comparison, the Promise returned by Promise.all() may be more appropriate if the tasks are dependent on each other / if you'd like to immediately reject upon any of them rejecting.
Promise.all([Promise1, Promise2])
.then(result) => {
console.log(result)
})
.catch(error => console.log(`Error in promises ${error}`))
Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
You can use promise.all or promise.allSettled
If your use case is like if any one of the request fails then operation needed to be fail means then its good to use promise.all
If you need to keep waiting for all, regardless of any in between request fails or not then you need to use promise.allSettled.
But both of these wont ensure serializability. If you need to ensure serializable i would prefer to use for await loop and use promise.resolve inside it.
I'm a bit confused how promise.all work, does it run the array of promises in parallel?
So here is a sample code
// index.js
const getSomething = async (args) => {
return await apiCallHere(args)
}
// Create Array of Promises
const arrayOfPromises = sampleArray.map(sample => new Promise((resolve, reject) => {
try {
const something = this.getSomething(sample, args)
resolve(something)
} catch (error) {
reject(error)
}
}))
await Promise.all(arrayOfPromises)
From what I observed, Promise.all runs the promises in parallel, and wait for all promises
to finish.
does it run the array of promises in parallel
Promise.all doesn't, no; your code does (well, probably; see the Notes below). The work is already underway before Promise.all sees the promises. What Promise.all does is give you a promise that will settle when all of the promises you give it are fulfilled (or one of them is rejected).
It's your code that makes the work run in parallel, by starting the actions that the promises report the completion of (in the map callback) in order to give them to Promise.all in the first place. See *** comments:
// *** `map` is synchronous, it loops all the way through the array
const arrayOfPromises = sampleArray.map(sample => new Promise((resolve, reject) => {
try {
const something = this.getSomething(sample, args) // *** This is what starts each thing
resolve(something)
} catch (error) {
reject(error)
}
}))
// *** The work is already underway here
// *** This just waits for it to finish
await Promise.all(arrayOfPromises)
Remember that a promise is just a way to observe the completion of an asynchronous process. Promises don't run anything. They just report the completion of something, along with the fulfillment value or rejection reason.
Notes
If this.getSomething(sample, args) returns a promise, your code is falling prey to the explicit promise creation anti-pattern: There's no reason to use new Promise here at all. Instead:
const arrayOfPromises = sampleArray.map(sample => this.getSomething(sample, args));
If this.getSomething(sample, args) returns its value immediately, then there's no point in using promises here at all, because the operations are already complete by the time it returns.
(I assume it doesn't start an asynchronous process and report completion via a callback instead of a promise, since you haven't shown a callback but you have shown using the return value.)
The getSomething you've shown in the question returns a promise (because it's an async function), but you wouldn't call that as this.getSomething(...), just as getSomething(...).
I have a case, where Im sending email if previous action succeeded.
Promise.all([doSomeAction(), sendMailIfSuccess()]) // both of them are promises
.then(() => success)
.catch(() => err);
However, if doSomeAction() promise fails before sendMailIfSuccess resolves, the mail is sent anyways. But it shouldnt.
Question: How to invoke sendMailIfSuccess promise only if doSomeAction resolved? The sendMailIfSuccess promise should await for the doSomeAction promise.
Since you want to run those two process in series, and run the second only if the first succeeds, Promise.all is not the right tool here - just use .then instead:
doSomeAction()
.then(() => sendMailIfSuccess())
.catch( /* handle errors, including doSomeAction failures */);
You can use async/await as well
async function f(){
let result1 = await doSomeAction()
let result2 = await sendMailIfSuccess()
}
try {
f()
} catch(err) {
//handle error
}
As mentioned by #CertainPerformance, if you want these methods to run in series. You should call the second method after getting a success response from the first one.
doSomeAction()
.then(res => {
/* do something with the res, if needed */
return sendMailIfSuccess();
})
.catch(err => {
/* handle errors */
});
Why Promise.all(...) was not the right choice?
As mentioned here
It is typically used after having started multiple asynchronous tasks to run concurrently and having created promises for their results, so that one can wait for all the tasks being finished.
Similarly, there is Promise.race(...) here, which is used when we need the first resolved or rejected in the iterable.
If I have an array of elements and I want to do parallel operations on them.
I would use promise.all().
I knew promise.all() accepts array of promises. Correct me if I am wrong, I don't think so.
Here, it clearly says.
The Promise.all() method returns a single Promise that fulfills when all of the promises passed as an iterable have been fulfilled or when the iterable contains no promises or when the iterable contains promises that have been fulfilled and non-promises that have been returned. It rejects with the reason of the first promise that rejects, or with the error caught by the first argument if that argument has caught an error inside it using try/catch/throw blocks.
So, yes we can pass simple functions to promise.all(), and it resolves if they return and rejects if they throw an error.
Now look at the below code.
const promises = todayAssignedJobs.map(async todayAssigned => {
const [leaderboard, created] = await Leaderboard.findOrCreate({...});
if (!created) {
const rating = (todayAssigned.rating + leaderboard.rating * leaderboard.jobs_completed) / (leaderboard.jobs_completed + 1);
const commission = todayAssigned.commission + leaderboard.commission;
const jobsCompleted = leaderboard.jobs_completed + 1;
await Leaderboard.update({
rating,
commission,
jobs_completed: jobsCompleted,
updated_by: 'system',
}, {
where: {
id: leaderboard.id,
},
});
}
await AssignedJob.update({
is_leaderboard_generated: true,
}, {
where: {
id: todayAssigned.id,
},
});
});
await Promise.all(promises);
Here, I have a doubt.
We are iterating on each element of the array and doing operation on them asyncrhonously. They don't return anything explicitly.
So, I think map is doing parallel operations here too.
Why shuold then use promise.all() here?
.map() is not promise-aware. So, when you pass it an async callback like you are, it does not pay any attention to that returned promise. So, it just runs the loop one after another, not waiting for any of the returning promises. Thus, all the asynchronous operations started in the .map() loop will be in-flight at the same time.
If that's what you want and you want to collect all the returned promises so you can later see when they are all done with Promise.all(), then this pattern works well:
Promise.all(someArray.map(callbackThatReturnsAPromiseHere))
And, this is a common design pattern for it. In fact, the Bluebird promise library has a special function that combined these two called Promise.map(). It also offered another nice feature that let you control how many concurrent async operations could run at once (because it's map() operation is promise-aware).
It sounds like you're trying to figure out if you should only use .map() and not use Promise.all(). If you do that, you will run your asynchronous operations in parallel, but you will not have any idea when they are all done or any ability to collect all the results. You would use Promise.all() on the array of returned promises to know when they are all done and/or to collect their resolved results.
FYI, .map() is JUST a plain loop. It doesn't have any special asynchronous features or any special run-in-parallel features. You can do the same thing with a for loop if you want. It does NOT pause for your async callback to wait for it to be done so a side effect of running it is that you launch a bunch of parallel asynchronous operations.
The purpose of Promise.all here is to be able to await all the promises before continuing.
If you added a console.log immediately before the await Promise.all(promises); it would run synchronously before any promises have resolved, whereas a console.log immediately after that line will only appear after all the promises have resolved.
You only need Promise.all if you want to do something when all operations are complete. For example:
const promises = todayAssignedJobs.map(async todayAssigned => {
// lots of async stuff
});
await Promise.all(promises);
// now, all Promises have resolved
// alert the user that the leaderboard is completely updated
If you don't need anything to occur once you've determined that all Promises have completed, then there's no point to the Promise.all - you may as well just create the Promises in a loop and leave them as-is. In that case, since you wouldn't be using the resulting array of Promises, it would be more appropriate to use forEach (which is the array method to use for side-effects).
There's one issue, though - you aren't handling errors, but errors should be handled, otherwise they'll give warnings or exit the Node process:
const processJob = async (todayAssigned) => {
const [leaderboard, created] = await Leaderboard.findOrCreate({...});
if (!created) {
// ...
// ...
todayAssignedJobs.forEach(async todayAssigned => {
try {
await processJob(todayAssigned);
} catch(e) {
// handle errors
}
});