This question already has answers here:
Waiting for more than one concurrent await operation
(4 answers)
Closed 4 years ago.
How do I retrieve the result of a promise at a later time? In a test, I am retrieving an email before sending further requests:
const email = await get_email();
assert.equal(email.subject, 'foobar');
await send_request1();
await send_request2();
How can I send the requests while the slow email retrieval is going on?
At first, I considered awaiting the email later:
// This code is wrong - do not copy!
const email_promise = get_email();
await send_request1();
await send_request2();
const email = await email_promise;
assert.equal(email.subject, 'foobar');
This works if get_email() is successful, but fails if get_email() fails before the corresponding await, with a completely justified UnhandledPromiseRejectionWarning.
Of course, I could use Promise.all, like this:
await Promise.all([
async () => {
const email = await get_email();
assert.equal(email.subject, 'foobar');
},
async () => {
await send_request1();
await send_request2();
},
]);
However, it makes the code much harder to read (it looks more like callback-based programming), especially if later requests actually depend on the email, or there is some nesting going on. Is it possible to store the result/exception of a promise and await it at a later time?
If need be, here is a testcase with mocks that sometimes fail and sometimes work, with random timings. It must never output UnhandledPromiseRejectionWarning.
const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const send_request1 = () => wait(300), send_request2 = () => wait(200);
async function get_email() {
await wait(Math.random() * 1000);
if (Math.random() > 0.5) throw new Error('failure');
return {subject: 'foobar'};
}
const assert = require('assert');
async function main() {
// catch possible error
const email_promise = get_email().catch(e => e);
await send_request1();
await send_request2();
// wait for result
const email = await email_promise;
// rethrow eventual error or do whatever you want with it
if(email instanceof Error) {
throw email;
}
assert.equal(email.subject, 'foobar');
};
(async () => {
try {
await main();
} catch(e) {
console.log('main error: ' + e.stack);
}
})();
In case it's guaranteed that promise rejection will be handled later, a promise can be chained with dummy catch to suppress the detection of unhandled rejection:
try {
const email_promise = get_email();
email_promise.catch(() => {}); // a hack
await send_request1();
await send_request2();
const email = await email_promise;
assert.equal(email.subject, 'foobar');
} catch (err) {...}
The problem with this approach is that there are two concurrent routines but the code doesn't express this, this is a workaround for what is usually done with Promise.all. The only reason why this workaround is feasible is that there are only 2 routines, and one of them (get_email) requires to be chained with then/await only once, so a part of it (assert) can be postponed. The problem would be more obvious if there were 3 or more routines, or routines involved multiple then/await.
In case Promise.all introduces unwanted level of lambda nesting, this can be avoided by writing routines as named functions, even if they aren't reused anywhere else:
async function assertEmail() {
const email = await get_email();
assert.equal(email.subject, 'foobar');
}
async function sendRequests() {
await send_request1();
await send_request2();
}
...
try {
await Promise.all([assertEmail(), sendRequests()]);
} catch (err) {...}
This results in clean control flow and verbose but more intelligible and testable code.
So, I want to explain why we behave this way in Node.js:
// Your "incorrect code" from before
const email_promise = get_email(); // we acquire the promise here
await send_request1(); // if this throws - we're left with a mess
await send_request2(); // if this throws - we're left with a mess
const email = await email_promise;
assert.equal(email.subject, 'foobar');
That is, the reason we behave this way is to not deal with the "multiple rejections and possibly no cleanup" scenario. I'm not sure how you ended up with the long code for Promise.all but this:
await Promise.all([
async () => {
const email = await get_email();
assert.equal(email.subject, 'foobar');
},
async () => {
await send_request1();
await send_request2();
},
]);
Can actually be this:
let [email, requestData] = await Promise.all([
get_email(),
send_request1().then(send_request2)
]);
// do whatever with email here
It's probably what I would do.
Related
I have a function that looks like this:
async function sync(req, res, done){
await createRecords().then(async ()=> {
await Promise.all(
[
quantityReport(),
listings(),
productIdentifiers(),
productChildren()
])
}).then(async ()=>{
await saveAll()
} ).then(await createCSV);
}
module.exports = sync
I am calling it like this inside a switch:
// const saveRecords = require('../scripts/saveRecords.js') <- for reference
await saveRecords;
My problem is that the program continues before saveRecords finishes and I cannot figure out why.
All of the functions in Promise.all are asynchronous functions.
If I call sync() directly in saveRecords.js it works fine.
Thanks.
edit
createCSV also works fine in other locations in the program. It's imported to this file like this:
const {createCSV, uploadReports} = require('../scripts/createCSV.js')
//in createCSV.js
module.exports = createCSV;
I'd refactor your function as such (by the way, sync doesn't sounds like a great name for your function, write something more obvious).
async function sync(req, res, done){
try{
await createRecords()
const _res = await Promise.all([
quantityReport(),
listings(),
productIdentifiers(),
productChildren()
])
if(_res) {
await saveAll()
await createCSV()
}
return
}
catch(err){
throw new Error(err)
}
}
module.exports = sync
As I mentioned in the comments using async/await with then from the Promise API (see also fetch) is an odd thing to do. Use one or the other. But the key issue is that you're not calling the sync function await sync().
Here's an quick example of simply using async/await.
function mockCall(n) {
return new Promise((res, rej) => {
setTimeout(() => res(n), 1000);
});
}
async function sync() {
const first = await mockCall(1);
const twoToFive = await Promise.all([
mockCall(2),
mockCall(3),
mockCall(4),
mockCall(5)
]);
const six = await mockCall(6);
const seven = await mockCall(7);
console.log([ first, twoToFive, six, seven ]);
}
(async function main() {
await sync();
console.log('Let the processing resume!');
})();
I have two blocks of code. First is using async await
async sendEmailNotifications() {
try {
const users = await User.find(...)
const promises = users.map(async(user) => {
const _promises = user.appId.map(async(app) => {
const todayVisitorsCount = await Session.count({...})
const yesterdayVisitorsCount = await UserSession.count({...})
const emailObj = {
todayVisitorsCount,
yesterdayVisitorsCount
}
const sendNotification = await emailService.analyticsNotification(emailObj)
})
await Promise.all(_promises)
})
return promises
} catch (err) {
return err
}
}
(await sendEmailNotifications())
And then I have using Promise.all
sendEmailNotifications() {
const users = await User.find(...)
const promises = users.map((user) => {
const allPromises = []
user.appId.map((app) => {
allPromises.push(UserSession.count({...}))
allPromises.push(Session.count({...}))
})
const data = await Promise.all(allPromises)
const emailObj = {
todayVisitorsCount: data[0],
yesterdayVisitorsCount: data[1]
}
const sendNotification = await emailService.analyticsNotification(emailObj)
})
return promises
}
sendNotification.then((data) => console.log(data))
Now I need to know which piece of code will faster execute? One is with series(async await) and one is with parellel(Promise.all). Which has better performance?
In the first code, you have two separate await statements:
const todayVisitorsCount = await Session.count({...})
const yesterdayVisitorsCount = await UserSession.count({...})
whereas in the second, you only have one, before a Promise.all:
const data = await Promise.all(allPromises)
In the first code, the second Promise will only initialize after the first Promise has finished, resulting in a longer time required before the script ends. For example:
const fn = () => new Promise(resolve => setTimeout(resolve, 1000));
console.log('start');
(async () => {
await fn();
await fn();
console.log('two awaits done');
})();
(async () => {
await Promise.all([fn(), fn()]);
console.log('Promise.all done');
})();
The version without Promise.all pauses the function when the first call of fn() is made, and waits for the Promise returned by fn() to resolve (1000 ms) before proceeding to the next line. The next line calls fn() again, and the await waits for it to complete (1000 more ms).
In contrast, the Promise.all version calls both fn()s immediately - both Promises are initialized, and the await that pauses the function is waiting for both Promises to complete. There's no down time between the initialization of the first Promise and the initialization of the second Promise.
So, the Promise.all version will run more significantly more quickly than the version with two awaits. Using Promise.all will be preferable unless the first Promise (UserSession.count) must be completed before the second Promise (Session.count) starts.
With destructuring and without unnecessary variables, this is how I would clean up your Promise.all code, you might consider it to be a bit more readable:
async sendEmailNotifications() {
const users = await User.find();
return users.map(async (user) => {
const [todayVisitorsCount, yesterdayVisitorsCount] = await Promise.all([
UserSession.count(),
Session.count()
]);
await emailService.analyticsNotification({ todayVisitorsCount, yesterdayVisitorsCount });
});
}
is there any .catch() method like there is with Promises for async await style of code?
Here's an example of a code written via Promise:
const apiURL = 'https://jsonplaceholder.typicode.com/todos/1';
const badURL = 'zhttps://wcaf.fajfkajf.gg'
function getData(url){
fetch(url)
.then(response => response.json())
.then(json => console.log(json))
.catch( err => console.log('cannot load api'))
}
getData(apiURL);
getData(badURL);
A simple function to try to load data and if not, display a basic error message. Now I was trying to transcribe this into async/await style code, issue was, I could not really figure out a way to write this with catch()
My best guess was to try try - catch but the catch part doesn't work:
const apiURL = 'https://jsonplaceholder.typicode.com/todos/1';
const badURL = 'zhttps://wcaf.fajfkajf.gg'
async function getData(url){
const response = await fetch(url);
try {
const json = await response.json();
console.log(json);
} catch (e) {
console.log('cannot load api');
}
}
getData(apiURL);
getData(badURL);
This loads the object API just fine, but never seems to go into the catch{} block despite being passed incorrect url.
Any idea what am I doing wrong?
As pointed out in the comments by #l-portet, this is because the code inside try { } block does not actually fail!
.json() will return a promise, regardless of the content of the parsed body text, so even though the initial fetch() fails, you can still call .json() on it - albeit it's completely redundant as it won't return anything meaningful.
Putting the fetch() request inside the try { } block does result in the expected behaviour:
const apiURL = 'https://jsonplaceholder.typicode.com/todos/1';
const badURL = 'zhttps://wcaf.fajfkajf.gg'
async function getData(url){
try {
const response = await fetch(url);
const json = await response.json();
console.log(json);
} catch (e) {
console.log('cannot load api');
}
}
getData(apiURL);
getData(badURL);
One thing you should be aware is that when an async function is executed, it always returns a promise, regardless the exit condition of the function.
If the function has an explicit return (or completes without crashing) the promise will be resolved to the value it returned (or to undefined if there was no explicit return), if the function throws, the promise will be rejected, passing the thrown error object.
Knowing that you could simply handle the error where you use the function, for example:
const apiURL = 'https://jsonplaceholder.typicode.com/todos/1';
const badURL = 'zhttps://wcaf.fajfkajf.gg'
async function getData(url){
const response = await fetch(url);
return await response.json();
}
getData(apiURL).then(data => console.log(data));
getData(badURL).catch(err => console.log('error:', err));
IMHO handling the error closely where you have a use-case of the function makes more sense, since normally when you expect to have an error is because we have a way to handle it (maybe try another API url in this example).
One pattern that I've been using lately is to wrap promises in a way they resolve returning a tuple, in the convention of [error, value] (similar to the way the Go programming language handle async error), in that way for instance you could handle the error in the specific getData call, for example:
const apiURL = 'https://jsonplaceholder.typicode.com/todos/1';
const badURL = 'zhttps://wcaf.fajfkajf.gg'
async function getData(url){
const response = await fetch(url);
return await response.json();
}
// simple utility function
const safePromise = promise =>
promise.then(data => [null, data]).catch(err => [err, undefined]);
(async () => {
const [err, json] = await safePromise(getData(apiURL))
if (err) {
// handle the error
}
console.log(json)
const [error, data] = await safePromise(getData(badURL))
if (error) {
console.log('Error:', error);
}
})()
Check the following library which basically ships this pattern:
await-to-js
I've got into the pattern of using async await in my aws nodejs lambda functions, and I common thing I run into is the need to await the result of a promise and use the response in the next async/await promise, and sort of repeat this pattern until I've run all my logic.
let userId;
await GetUserId({AccessToken: headers.accesstoken}).then(function(res){
userId = res;
},function(err){
});
let username;
await GetUserName(userId).then(function(res){
username = res;
},function(err){
});
Is there anyway I can declare and assign userId a value in the same line as invoking the function.
sudo code:
let userId = await GetUserId().then(()=>{ //bubble response up to userId })
The reason I'm asking is that it just sort of messing/wrong initializing a variable separately. Maybe I need a different pattern, or it's just something I'll have to live with.
Solution
var ExampleFunction = async() => {
try {
const userId = await GetUserId('token');
const username = await GetUserName(userId);
console.log(`userId: ${userId}`);
console.log(`username: ${username}`);
} catch (err) {
console.log(err);
console.log(`Exit Function`);
}
function GetUserId(token) {
return new Promise(function(resolve, reject) {
if (!token)
reject('no token');
resolve('ID');
});
}
function GetUserName(userId) {
return new Promise(function(resolve, reject) {
if (!userId)
reject('no userId');
resolve('NAME');
});
}
}
ExampleFunction();
The await is supposed to replace the then syntax (except you really need to distinguish fulfillment from rejection with it). The await expression either throws the rejection reason as an exception to catch, or results in the fulfilment value of the promise. You can directly use that:
const userId = await GetUserId({AccessToken: headers.accesstoken});
const username = await GetUserName(userId);
(I assume that it was unintentional that your current code ignored errors and continued with undefined values).
The keyword await makes JavaScript wait until that promise resolves and returns its result.
let userId = await GetUserId({AccessToken: headers.accesstoken});
if you assign the result of await to var it will be the promise resolve value
so you can
let userId = await GetUserId({AccessToken: headers.accesstoken});
and
let username = await GetUserName(userId);
PS. don't forget error handling using try/catch.
I was looking at how to use transactions in:
https://node-postgres.com/features/transactions
But in the following code example:
const { Pool } = require('pg')
const pool = new Pool()
(async () => {
// note: we don't try/catch this because if connecting throws an exception
// we don't need to dispose of the client (it will be undefined)
const client = await pool.connect()
try {
await client.query('BEGIN')
const { rows } = await client.query('INSERT INTO users(name) VALUES($1) RETURNING id', ['brianc'])
const insertPhotoText = 'INSERT INTO photos(user_id, photo_url) VALUES ($1, $2)'
const insertPhotoValues = [res.rows[0].id, 's3.bucket.foo']
await client.query(insertPhotoText, insertPhotoValues)
await client.query('COMMIT')
} catch (e) {
await client.query('ROLLBACK')
throw e
} finally {
client.release()
}
})().catch(e => console.error(e.stack))
It seems that the function will execute immediately. Also there doesn't seem to be a way to specify a callback. Would it make sense to place the entire block from "(async()...." into a function, and then in the final statement before the end of the try block, add :
await callbackfunction();
Does that make sense? What would be a better way to add a callback function ?
The point of await is that you don't use a callback. It returns the result of resolving the promise.
Without await:
do_something_asyc.then(function (data) { alert(data); });
With await:
var data = await do_something_asyc();
alert(data);