Timing when converting promise to async await - javascript

I have the following block
return this
.configure(config => {
viewModel.configure(config, this);
return config;
})
.then(() => {
this.activate();
});
which, is equivalent to following block, suggested by vscode auto promise->async/await conversion:
await this.configure(config => {
viewModel.configure(config, this);
return config;
});
this.activate();
My question is are they actually the same in timing? In the promise example, shouldn't the second fn in then() 1 micro task away from the callback that accepts config?
Extra question for clarity: are the following equal in timing perspective:
Promise.all(tasks)
.then(() => {
othertasks.forEach(() => doStuff());
})
.then(() => {
prune();
});
and:
await Promise.all(tasks);
othertasks.forEach(() => doStuff();
await Promise.resolve();
prune();

EDIT: I should clarify, with regards to Andrea Giammarchi's answer, that my answer was purely and only related to the difference in number of ticks in-between the synchronously executed code, not whether the result of that code as-written is actually equivalent (obviously, the non-awaited promises would yield promises whereas the awaited promises would yield the values)
This would make more sense in the context that me and bigopon had a discussion in a github issue where he accepted VS Code's suggestion to remove a "redundant" .then from a promise chain in a piece of legacy code that happens to be sensitive to subtle timing issues.
I pointed out that this change would cause a particular method to be executed one tick earlier, and that the effects of that could potentially break complex apps relying on these (quirky) timings.
The discussion was then, whether this:
somePromise.then(() => {
...
}).then(() => {
doStuff();
})
Would have the same timings as this:
await somePromise;
doStuff();
To which my answer was: no, the doStuff() in the second snippet would execute one tick earlier.
When someone else suggested that await or .then would actually be executed synchronously if the passed-in promise was already resolved, that motivated me to write this answer and clarify why not.
I do realize that without this context, my answer can seem misleading, but again: it's just to point out the similarity in number of ticks.
Original answer
Example 1
For resolving a value, in plain terms, this:
await something
Is equivalent to this:
Promise.resolve(something).then()
They both result in a pending promise.
Example 2
For queueing a task, this:
await Promise.resolve();
doStuff();
Is equivalent to this:
Promise.resolve().then(() => {
doStuff();
})
In both cases, doStuff() happens on the next tick.
In order to determine whether a regular .then chain is equivalent to a series of awaits, you simply need to count .then and await. If the number of each is the same between two given pieces of code, then the amount of time/ticks/whatever passing between those pieces of code will be the same.
Example 3
Another example, this:
await Promise.resolve();
doStuff();
await Promise.resolve();
doStuff();
await Promise.resolve();
await Promise.resolve();
doStuff();
Is equivalent to this:
Promise.resolve()
.then(() => {
doStuff();
})
.then(() => {
doStuff();
})
.then(() => {})
.then(() => {
doStuff();
})
Note that the Promise.resolve() itself has no effect on the timings. It returns a resolved promise. It's the then() / await that turns it into a pending one.
So I respectfully disagree with amadan and I believe both your examples are equivalent.
What the spec says
If promise.[[PromiseState]] is "pending", then
a. Append fulfillReaction as the last element of the List that is promise.[[PromiseFulfillReactions]].
b. Append rejectReaction as the last element of the List that is promise.[[PromiseRejectReactions]].
Else if promise.[[PromiseState]] is "fulfilled", then
a. Let value be promise.[[PromiseResult]].
b. Perform EnqueueJob("PromiseJobs", PromiseReactionJob, « fulfillReaction, value »).
What this says is "if the promise is already pending, simply append the fulfillReaction of that pending promise, but of the promise is fulfilled, then enqueue a new job".
In other words, .then is guaranteed to return a pending promise regardless of whether the promise it is chained on was fulfilled or not.

I think there is quite some confusion in both what happened with VSCode, what you asked as question, and the kind of answer you received.
I'll try to clarify all points as I can, hoping I got the question right.
Let me start saying that ...
Those two blocks are not equivalent
The following piece of code:
this
.configure(config => {
viewModel.configure(config, this);
return config;
})
.then(value => `some ${value}`);
is "equivalent" only to this one:
await this
.configure(config => {
viewModel.configure(config, this);
return config;
})
.then(value => `some ${value}`);
That is because await has less priority than method chaining/then concatenation.
(async function (){
const then = await Promise.resolve(1).then(one => one + one);
console.log(then); // this is number 2
const something = await 123;
console.log(something); // this is number 123
}());
The reason you are rightly confused is that VSCode outsmarted your intent.
return this
.configure(config => {
viewModel.configure(config, this);
// configure() returns a Promise
// and config can be a Promise too
return config;
})
.then(() => {
// but you are not using here the config value
// or, if it was a promise, whatever value it resolved
// and you are also not returning any value
this.activate();
});
Since VSCode knows that configure is thenable, and its returned value could also be a Promise, which would imply the activate can happen only after config is eventually resolved, it also knows having an extra tick would make no sense because you don't need whatever config returned, either as value or promise, so that activate can be called right away.
Since you were also not returning any value in the last then, the whole return can be dropped.
// only async to wait for
await this.configure(config => {
viewModel.configure(config, this);
return config;
});
// with config either value or promise
// there's nothing else to wait for, so
// let's invoke activate without returning
// anything, producing is the same undefined result
this.activate();
To recap what happens inside that await:
(async function (){
const config = new Promise(res => setTimeout(res, 1000));
console.time('awaiting');
const value = await Promise.resolve(1).then(() => {
return config;
});
console.timeEnd('awaiting');
// awaiting: 1000.XXXms
}());
If you were by any chance using the returned value inside that last then, you would've seen that VSCode could not have dropped it, most-likely readdressed as const value = await ...; this.activate(value); which is also still OK.
To the previous comment stating:
For resolving a value, in plain terms, this:
await something
Is equivalent to this:
Promise.resolve(something).then()
They both result in a pending promise.
Not sure I read that wrong but that that felt to me quite a misleading statement.
const resolved = await anything means resolved is always a value, never a pending promise.
That's quite possibly the whole point of await: it won't stop awaiting until there is a value.
Example:
(async function (){
const something = Promise.resolve(Math.random());
// this logs the random number as typeof number
console.log(await something);
// this also logs the random number as typeof number
console.log(await Promise.resolve(something).then());
// while this is one is the only pending promise
console.log(Promise.resolve(something).then());
}());
The reason you eventually see Pending promise in console is that the AIIFE (Asynchronous Immediately Invoked Function Expression) is a promise itself and you can await it elsewhere.
You can see that returning a value or a pending promise will always produce the expected result.
(async function (){
// instant return, it's just fine
// return 123;
// return promise (unnecessary ticks added)
return Promise.resolve(123).then();
}()).then(console.log);
In both cases the 123 number is logged.
I hope it's clear now, specially for the OP, what happened in VSCode, and, specially, why happened.
Regards.

Related

Why to use Promise.resolve().then()?

While reading Angular's code for the directive RouterLinkActive, I found a curious usage of Promise.resolve:
private update(): void {
...
Promise.resolve().then(() => {
const hasActiveLinks = this.hasActiveLinks();
if (this.isActive !== hasActiveLinks) {
...
}
});
}
In which case is it useful to use Promise.resolve().then() instead of just executing the code that is inside of the then?
I've seen usages of setTimeout(() => defeferedFunction()) but first time seeing it with Promise.resolve.
The Angular framework does this a lot. It is used to prevent an error that you will see a lot (sorry don't know the actual wording of the error) something like 'The value was changed after it was checked'. This error happens if the Change Detector checked a value and then it was changed in the same cycle.
The Promise.resolve() pushes this code on to the microtask queue so it is executed after the JavaScript stack is empty. This prevents the error.
If you want your function to return a promise, but you only conditionally run code that involves promises, you may want to use this pattern.
Alternatively, just use async/await syntax.
So for example, if you have a function to fetch data, but the data may be cached, you can do something like
function fetchData() {
return Promise.resolve().then(function() {
if (cache && cache.data) return cache.data;
// if we're here, the data isn't cached.
return database.getData();
})
}
This way, it ALWAYS returns a promise that always resolves with the data you want, even when you're using the sychronous cache code
In simple terms, inside a then handler function:
A) When x is a value (number, string, etc):
return x is equivalent to return Promise.resolve(x)
throw x is equivalent to return Promise.reject(x)
B) When x is a Promise that is already settled (not pending anymore):
return x is equivalent to return Promise.resolve(x), if the Promise was already resolved.
return x is equivalent to return Promise.reject(x), if the Promise was already rejected.
C) When x is a Promise that is pending:
return x will return a pending Promise, and it will be evaluated on the subsequent then.

Async / await vs then which is the best for performance?

I have a simple code in JavaScript that execute a request in an API and return the response, simple. But in this case I will have thousands of requests. So, which one of the code options will perform better, and why. Also which one is recommended as good pratices these days?
First options is using the .then to resolve the promises and the seccond one is using async / await.
In my tests the two options had very similar results without significant differences, but I'm not sure in scale.
// Using then
doSomething(payload) {
const url = 'https://link-here/consultas';
return this.axios.get(url, {
params: {
token: payload.token,
chave: payload.chave,
},
}).then(resp => resp.data);
}
// Using Async / await
async doSomething(payload) {
const url = 'https://link-here/consultas';
const resp = await this.axios.get(url, {
params: {
token: payload.token,
chave: payload.chave,
},
});
return resp.data;
}
Any explanation will be of great value.
From a performance point of view, await is just an internal version of .then() (doing basically the same thing). The reason to choose one over the other doesn't really have to do with performance, but has to do with desired coding style or coding convenience. Certainly, the interpreter has a few more opportunities to optimize things internally with await, but its unlikely that should be how you decide which to use. If all else was equal, I would choose await for the reason cited above. But, I'd first choose which made the code simpler to write and understand and maintain and test.
Used properly, await can often save you a bunch of lines of code making your code simpler to read, test and maintain. That's why it was invented.
There's no meaningful difference between the two versions of your code. Both achieve the same result when the axios call is successful or has an error.
Where await could make more of a convenience difference is if you had multiple successive asynchronous calls that needed to be serialized. Then, rather than bracketing them each inside a .then() handler to chain them properly, you could just use await and have simpler looking code.
A common mistake with both await and .then() is to forget proper error handling. If your error handling desire in this function is to just return the rejected promise, then both of your versions do that identically. But, if you have multiple async calls in a row and you want to do anything more complex than just returning the first rejection, then the error handling techniques for await and .then()/.catch() are quite different and which seems simpler will depend upon the situation.
There should be some corrections in this thread. await and .then are going to give very different results, and should be used for different reasons.
await will WAIT for something, and then continue to the next line. It's also the simpler of the two because it behaves mechanically more like synchronous behavior. You do step #1, wait, and then continue.
console.log("Runs first.");
await SomeFunction();
console.log("Runs last.");
.then splits off from the original call and starts operating within its own scope, and will update at a time the original scope cannot predict. If we can put semantics aside for a moment, it's "more asynchronous," because it leaves the old scope and branches off into a new one.
console.log("Runs first.");
SomeFunction().then((value) => {console.log("Runs last (probably). Didn't use await on SomeFunction().")})
console.log("Runs second (probably).");
As more explanation to #user280209 answer let's consider the following function which returns promise and compare its execution with .then() and async await.
function call(timeout) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(`This call took ${timeout} seconds`);
resolve(true);
}, timeout * 1000);
});
}
With .then()
(async () => {
call(5).then((r) => {
console.log(r);
});
await call(2); //This will print result first
await call(1);
})();
When running the above call the logs will be
This call took 2 seconds
This call took 1 seconds
This call took 5 seconds
true
As we can see .then() didn't pause the execution of its below line until it completes.
With async/wait
(async () => {
await call(5); //This will print result first
await call(2);
await call(1);
})();
When run the above function logs will be
This call took 5 seconds
This call took 2 seconds
This call took 1 seconds
So I think if your promise's result won't be used in the following lines, .then() may be better.
For those saying await blocks the code until the async call returns you are missing the point. "await" is syntactic sugar for a promise.then(). It is effectively wrapping the rest of your function in the then block of a promise it is creating for you. There is no real "blocking" or "waiting".
run();
async function run() {
console.log('running');
makePromises();
console.log('exiting right away!');
}
async function makePromises() {
console.log('make promise 1');
const myPromise = promiseMe(1)
.then(msg => {
console.log(`What i want to run after the promise is resolved ${msg}`)
})
console.log('make promise 2')
const msg = await promiseMe(2);
console.log(`What i want to run after the promise is resolved via await ${msg}`)
}
function promiseMe(num: number): Promise<string> {
return new Promise((resolve, reject) => {
console.log(`promise`)
resolve(`hello promise ${num}`);
})
}
The await line in makePromises does not block anything and the output is:
running
make promise 1
promise
make promise 2
promise
exiting right away!
What i want to run after the promise is resolved hello promise 1
What i want to run after the promise is resolved via await hello promise 2
Actually.
Await/Async can perform more efficiently as Promise.then() loses the scope in which it was called after execution, you are attaching a callback to the callback stack.
What it causes is: The system now has to store a reference to where the .then() was called. In case of error it has to correctly point to where the error happens, otherwise, without the scope (as the system resumed execution after called the Promise, waiting to comeback to the .then() later) it isn't able to point to where the error happened.
Async/Await you suspend the exection of the method where it is being called thus preserving reference.
If we just consider performance(time taken) then it actually depends on whether your operations are serial or parallel. If your tasks are serial then there will be no difference between await and .then. But if your tasks are parallel then .then will take less time. Consider the following example
let time = Date.now();
// first task takes 1.5 secs
async function firstTask () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1);
},1500)
})
}
// second task takes 2 secs
async function secondTask () {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(2);
},2000)
})
}
// using await
async function call(){
const d1 = await firstTask();
const d2 = await secondTask();
console.log(Date.now()-time, d1+d2)
}
call()
// using .then
async function call2(){
let d1=null,d2=null;
firstTask().then(data => {
d1=data;
if(d2){
console.log(Date.now()-time, d1+d2);
}
})
secondTask().then(data => {
d2=data;
if(d1){
console.log(Date.now()-time, d1+d2);
}
})
}
call2()
Here are the two tasks, first takes 1.5 secs and second takes 2 secs. Call function uses await where as call2 function uses .then . The output is as follows
From call2 2012 3
From call 3506 3
I hope it helps.
As far as I understand .then() and await are not the same thing. An async function won't proceed with the next command until the promise is resolved/rejected since it's basically an implementation of generators. On the contrast, in the case of .then(), the execution of the function will proceed with the next command and the resolve/reject callback will be executed "when there's time" aka when the current event loop (not entirely sure about that part) will be completed.
tldr; on a single promise await and .then() behave similarly but when one promise needs another one to be resolved first then the two of them behave entirely different
Many answer have been provided to this question already. However, to point out key information in the answers above and from my understanding, note below point:
only use await when not handling error return
if no crucial need for error handling use await instead
use .then .catch if returned error message or data is crucial for debugging / or proper error handling instead of try catch for await
Choose any prefer method from code sample below
const getData = (params = {name: 'john', email: 'ex#gmail.com'}) => {
return axios.post(url, params);
}
// anywhere you want to get the return data
// using await
const setData = async () => {
const data = await getData();
}
// to handle error with await
const setData = async () => {
try {
const data = await getData();
}
catch(err) {
console.log(err.message);
}
}
// using .then .catch
const setData = () => {
var data;
getData().then((res) => {
data = res.data; console.log(data)
}).catch((err) => {
console.log(err.message);
});
}

What is a nice way to override the value a promise resolves to? [duplicate]

This question already has answers here:
How do I access previous promise results in a .then() chain?
(17 answers)
Closed 5 years ago.
I am writing a Node.js script to populate an SQL database with a test dataset.
In the promise chain illustrated in the code snippet below (the real code is a bit more hairy), the function insertData() requires the db object to be passed through from the previous stage. However, asynchronous calls inside dropAndCreateTables() on the previous stage use db object but do not return it. I came up with a solution that wraps promises inside dropAndCreateTables() into another promise object that resolves to a db object. However:
I heard that using Promise() constructor in non-library code is an antipattern and may lead to subtle and hard-to-diagnose mistakes
I heard that nesting then()-chains is also an antipattern
It does not allow me to ignore errors from the promiseDrop (for example, I don't care if tables don't exist on drop)
It is ugly
Questions:
Is there a simpler, nicer and more socially accepted way to override the return value of a promise? (in this case, created with Promise.all())
Is there a way to restructure my code in a way that this problem does not occur? (That is, I don't exclude the possibility of "XY problem" here)
Code:
const dropAndCreateTables = (db, startClean) => {
if(startClean) {
const sqlDrop = fs.readFileSync('drop.sql').toString()
const promiseDrop = db.raw(sqlDrop)
const sqlCreate = fs.readFileSync('create.sql').toString()
const promiseCreate = db.raw(sqlCreate)
/********* Problems here? ************************************/
return new Promise((resolve, reject) => { // Ew?
Promise.all([promiseDrop, promiseCreate])
.then(() => {
resolve(db) // Override the returned value
})
.catch(reject)
})
}
return Promise.resolve(db)
}
initDB({ debug: false })
.then((db) => {
return dropAndCreateTables(db, START_CLEAN) // Without my hack this does not return `db`
})
.then((db) => {
return insertData(db, DO_UPSERT) // This needs the `db` object
})
.then(() => {
console.info(`\n${timestamp()} done`)
})
.catch(handleError)
(Some fairly important notes midway and later in the answer, please do read all the way to the end.)
Is there a simpler, nicer and more socially accepted way to override the return value of a promise? (in this case, created with Promise.all())
Yes, you simply return a value from the then handler, and return the promise then returns:
return Promise.all([promiseDrop, promiseCreate])
.then(() => db);
then (and catch) create promise chains. Each link in the chain can transform the result. then and catch return a new promise that will be fulfilled or rejected based on what happens in their callback:
If their callback throws, the promise rejects with the error thrown
If their callback returns a non-thenable value (e.g., promise), the promise is fulfilled with that value
If their callback returns a thenable value, the promise is resolved to that thenable — it waits for the other promise to settle, then settles the same way
(If the term "thenable" isn't familiar, or you're not clear on the distinction between "fulfill" and "resolve," I go into promise terminology in this post on my blog.)
I heard that using Promise() constructor in non-library code is an antipattern and may lead to subtle and hard-to-diagnose mistakes
The distinction isn't library code vs. non-library code, it's between code that doesn't already have a promise to work with and code that does. If you already have a promise to work with, you almost never want to use new Promise. More: What is the explicit promise construction antipattern and how do I avoid it?
I heard that nesting then()-chains is also an antipattern
You almost never need to nest then chains, because again each link in the chain already has the means of tranforming the result passing through it. So:
// Unnecessary nesting
doSomething()
.then(a => {
return doSomethingElse(a * 2)
.then(b => b * 3);
})
.catch(e => { /*...handle error...*/ });
can be more idiomatically and simply written:
doSomething()
.then(a => doSomethingElse(a * 2))
.then(b => b * 3);
.catch(e => { /*...handle error...*/ });
Is there a way to restructure my code in a way that this problem does not occur? (That is, I don't exclude the possibility of "XY problem" here)
Not an X/Y per se, but you have a problem in that code: There's no guarantee the drop will happen before the create! So instead of starting both and letting them run in parallel and watching for the results with Promise.all, ensure those operations happen in sequence:
// Fairly minimal changes
const dropAndCreateTables = (db, startClean) => {
if(startClean) {
const sqlDrop = fs.readFileSync('drop.sql').toString()
return db.raw(sqlDrop)
.then(() => {
const sqlCreate = fs.readFileSync('create.sql').toString()
return db.raw(sqlCreate);
})
.then(() => db);
}
return Promise.resolve(db)
}
But, I wouldn't use sync file I/O. Instead
const promisify = require("utils").promisify;
const readWithPromise = promisify(fs.readFile);
and then
const dropAndCreateTables = (db, startClean) => {
if(startClean) {
const getDrop = readWithPromise('drop.sql'); // Start this first
const getCreate = readWithPromise('create.sql'); // Then start this
return getDrop
.then(dropSql => db.raw(dropSql)) // Got the drop SQL, run it
.then(() => getCreate) // Make sure we have the create SQl
.then(createSql => db.raw(createSql)) // Run it
.then(() => db);
}
return Promise.resolve(db)
}
Note how we avoid ever busy-waiting on I/O, and we can overlap the DB's drop operation with reading the create SQL.
You don't need to call the Promise constructor when returning another promise, you can just write it like:
return Promise.all([promiseDrop, promiseCreate])
.then(() => db)
.catch(error => {
// handle the error or rethrow it
})
You might omit resolving db from dropAndCreateTables like this:
.then((db) => {
return dropAndCreateTables(db, START_CLEAN).then(Promise.resolve(db));
})
You should not let dropAndCreateTables return a db promise, there is no real usecase for it. So:
return Promise.all([promiseDrop, promiseCreate]);
is enough. Now the chaining part:
initDB({ debug: false }).then(async (db) => {
await dropAndCreateTables(db, START_CLEAN);
await insertData(db, DO_UPSERT);
console.info(`\n${timestamp()} done`)
}).catch(handleError)

Is it legitimate to omit the 'await' in some cases?

I am using async/await in several places in my code.
For example, if I have this function:
async function func(x) {
...
return y;
}
Then I always call it as follows:
async function func2(x) {
let y = await func(x);
...
}
I have noticed that in some cases, I can omit the await and the program will still run correctly, so I cannot quite figure out when I must use await and when I can drop it.
I have concluded that it is "legitimate" to drop the await only directly within a return statement.
For example:
async function func2(x) {
...
return func(x); // instead of return await func(x);
}
Is this conclusion correct, or else, what am I missing here?
EDIT:
A small (but important) notion that has not been mentioned in any of the answers below, which I have just encountered and realized:
It is NOT "legitimate" to drop the await within a return statement, if the called function may throw an exception, and that statement is therefore executed inside a try block.
For example, removing the await in the code below is "dangerous":
async function func1() {
try {
return await func2();
}
catch (error) {
return something_else;
}
}
The reason is that the try block completes without an exception, and the Promise object returns "normally". In any function which calls the outer function, however, when this Promise object is "executed", the actual error will occur and an exception will be thrown. This exception will be handled successfully in the outer function only if await is used. Otherwise, that responsibility goes up, where an additional try/catch clause will be required.
If func is an async function then calling it with and without await has different effects.
async function func(x) {
return x;
}
let y = await func(1); // 1
let z = func(1) // Promise (resolves to 1)
It is always legitimate to omit the await keyword, but means you will have to handle the promises in the traditional style instead (defeating the point of async/await in the first place).
func(1).then(z => /* use z here */)
If your return statements use await then you can be sure that if it throws an error it can be caught inside your function, rather than by the code that calls it.
await just lets you to treat promises as values, when used inside an async function.
On the other hand, async works quite the opposite, it tags the function to return a promise, even if it happens to return a real, synchronous value (which sounds quite strange for an async function... but happens often when you have a function that either return a value or a promise based on conditions).
So:
I have concluded that it is "legitimate" to drop the await only directly within a return statement.
In the last return statement of an async function, you just are returning a Promise, either you are return actually a directly a promise, a real value, or a Promise-as-value with the await keyword.
So, is pretty redundant to use await in the return statement: you're using await to cast the promise to a value -in the context of that async execution-, but then the async tag of the function will treat that value as a promise.
So yes, is always safe to drop await in the last return statement.
PS: actually, await expects any thenable, i.e. an object that has a then property: it doesn't need a fully spec compliant Promise to work, afaik.
PS2: of course, you can always drop await keyword when invoking synchronous functions: it isn't needed at all.
An async function always returns a Promise.
So please keep in mind that these writing of an async function are all the same:
// tedious, sometimes necessary
async function foo() {
return new Promise((resolve) => resolve(1)))
}
// shorter
async function foo() {
return Promise.resolve(1)
}
// very concise but calling `foo` still returns a promise
async function foo() {
return 1 // yes this is still a promise
}
You call all of them via foo().then(console.log) to print 1. Or you could call them from another async function via await foo(), yet it is not always necessary to await the promise right away.
As pointed out by other answers, await resolves the promise to the actual return value statement on success (or will throw an exception on fail), whereas without await you get back only a pending promise instance that either might succeed or fail in the future.
Another use case of omitting (i.e.: being careful about its usage) await is that you might most likely want to parallelize tasks when writing async code. await can hinder you here.
Compare these two examples within the scope of an async function:
async function func() {
const foo = await tediousLongProcess("foo") // wait until promise is resolved
const bar = await tediousLongProcess("bar") // wait until promise is resolved
return Promise.resolve([foo, bar]) // Now the Promise of `func` is marked as a success. Keep in mind that `Promise.resolve` is not necessary, `return [foo, bar]` suffices. And also keep in mind that an async function *always* returns a Promise.
}
with:
async function func() {
promises = [tediousLongProcess("foo"), tediousLongProcess("bar")]
return Promise.all(promises) // returns a promise on success you have its values in order
}
The first will take significantly longer than the last one, as each await as the name implies will stop the execution until you resolve the first promise, then the next one.
In the second example, the Promise.all the promises will be pending at the same time and resolve whatever order, the result will then be ordered once all the promises have been resolved.
(The Bluebird promise library also provides a nice Bluebird.map function where you can define the concurrency as Promise.all might cripple your system.)
I only use await when want to work on the actual values. If I want just a promise, there is no need to await its values, and in some cases it may actually harm your code's performance.
I got a good answer above, here is just another explanation which has occurred to me.
Suppose I have this:
async function func(x) {
...
return y;
}
async function func2(x) {
...
return await func(x);
}
async function func3(x) {
let y = await func2(x);
...
}
The reason why I can safely remove the await in the return statement on func2, is that I already have an await when I call func2 in func3.
So essentially, in func3 above I have something like await await func(x).
Of course, there is no harm in that, so it's probably better to keep the await in order to ensure desired operation.

Are there performance concerns with `return await`?

I see there is an eslint rule, no-return-await, for disallowing return await.
In the rule's description, it states a return await adds "extra time before the overarching Promise resolves or rejects".
However, when I look at MDN async function docs, the "Simple Example" shows an example containing return await without any description of why this might be a performance problem.
Is return await an actual performance problem as the eslint docs suggest?
And if so, how?
No, there isn't any performance problem. It's just an unnecessary extra operation. It might take a bit longer to execute, but should be hardly noticeable. It's akin to return x+0 instead of return x for an integer x. Or rather, exactly equivalent to the pointless .then(x => x).
It doesn't do actual harm, but I'd consider it bad style and a sign that the author does not fully compre­hend promises and async/await.
However, there's one case where it make an important difference:
try {
…
return await …;
} …
await does throw on rejections, and in any case awaits the promise resolution before catch or finally handlers are executed. A plain return would have ignored that.
I'm adding an answer because a comment would be too long. I originally had a very long, verbose explanation about how async works and await work. But it's just so convoluted that actual data may just be easier to understand. So here is the, uh, simplified explanation. Note: This is run on Chrome v97, FireFox v95, and Node v16 with the same results.
The answer as to what's faster: it depends on what you're returning and how you're calling it. await works differently than async because it runs PromiseResolve (similar to Promise.resolve but it's internal). Basically, if you run await on a Promise (a real one, not a polyfill), await doesn't try to wrap it. It executes the promise as is. That skips a tick. This is a "newer" change from 2018. In summary, evaluating await always returns the result of a Promise, not a Promise, while avoiding wrapping Promises when possible. That means await always takes at least one tick.
But that's await and async doesn't actually use this bypass. Now async uses the good ol' PromiseCapability Record. We care about how this resolves promises. The key points are it'll instantly start fulfilling if the resolution is "not an Object" or if .then is not Callable. If neither are true, (you're returning a Promise), it'll perform a HostMakeJobCallback and tack on to the then in the Promise, which basically means, we're adding a tick. Clarified, if you return a Promise in an async function, it'll add on an extra tick, but not if you return a Non-Promise.
So, with all that preface (and this is the simplified version), here's your chart as to how many ticks until your await foo() call is returned:
Non-Promise
Promise
() => result
1
1
async () => result
1
3
async () => await result
2
2
This is tested with await foo(). You can also test with foo().then(...), but the ticks are the same. (If you don't use an await, then the sync function would indeed be 0. Though foo().then would crash, so we need something real to test with.) That means our floor is 1.
If you understood my explanations above (hopefully), this will make sense. The sync function makes sense because at no point in the function do we call for paused execution: await foo() will take 1 tick.
async likes Non-Promises and expects them. It will return immediately if it finds one. But if it finds a Promise, it'll tack on to that Promise's then. That means it'll execute the Promise (+1) and then wait for the then to complete (another +1). That's why it's 3 ticks.
await will convert a Promise to a Non-Promise which is perfect for async. If you call await on a Promise, it'll execute it without tacking any extra ticks (+1). But, await will convert a Non-Promise into a Promise and then run it. That means await always takes a tick, regardless of what you call it against.
So, in conclusion, if you want the fastest execution, you want to make sure your async function always includes at least one await. If it doesn't, then just make it synchronous. You can always call await on any synchronous function. Now, if you want to really tweak performance, and you are going to use async, you have to make sure always return a Non-Promise, not a Promise. If you are returning a Promise, convert it first with await. That said you can mix and match like this:
async function getData(id) {
const cache = myCacheMap.get(id);
if (cache) return cache; // NonPromise returns immediately (1 tick)
// return fetch(id); // Bad: Promise returned in async (3 ticks)
return await fetch(id); // Good: Promise to NonPromise via await (2 ticks)
}
With that in mind, I have a bunch of code to rewrite :)
References:
https://v8.dev/blog/fast-async
https://tc39.es/ecma262/multipage/control-abstraction-objects.html#sec-async-functions-abstract-operations-async-function-start
Test:
async function test(name, fn) {
let tick = 0;
const tock = () => tick++;
Promise.resolve().then(tock).then(tock).then(tock);
const p = await fn();
console.assert(p === 42);
console.log(name, tick);
}
await Promise.all([
test('nonpromise-sync', () => 42),
test('nonpromise-async', async () => 42),
test('nonpromise-async-await', async () => await 42),
test('promise-sync', () => Promise.resolve(42)),
test('promise-async', async () => Promise.resolve(42)),
test('promise-async-await', async () => await Promise.resolve(42)),
]);
setTimeout(() => {}, 100);

Categories