Related
Given the code samples below, is there any difference in behavior, and, if so, what are those differences?
return await promise
async function delay1Second() {
return (await delay(1000));
}
return promise
async function delay1Second() {
return delay(1000);
}
As I understand it, the first would have error-handling within the async function, and errors would bubble out of the async function's Promise. However, the second would require one less tick. Is this correct?
This snippet is just a common function to return a Promise for reference.
function delay(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
Most of the time, there is no observable difference between return and return await. Both versions of delay1Second have the exact same observable behavior (but depending on the implementation, the return await version might use slightly more memory because an intermediate Promise object might be created).
However, as #PitaJ pointed out, there is one case where there is a difference: if the return or return await is nested in a try-catch block. Consider this example
async function rejectionWithReturnAwait () {
try {
return await Promise.reject(new Error())
} catch (e) {
return 'Saved!'
}
}
async function rejectionWithReturn () {
try {
return Promise.reject(new Error())
} catch (e) {
return 'Saved!'
}
}
In the first version, the async function awaits the rejected promise before returning its result, which causes the rejection to be turned into an exception and the catch clause to be reached; the function will thus return a promise resolving to the string "Saved!".
The second version of the function, however, does return the rejected promise directly without awaiting it within the async function, which means that the catch case is not called and the caller gets the rejection instead.
As other answers mentioned, there is likely a slight performance benefit when letting the promise bubble up by returning it directly — simply because you don’t have to await the result first and then wrap it with another promise again. However, no one has talked about tail call optimization yet.
Tail call optimization, or “proper tail calls”, is a technique that the interpreter uses to optimize the call stack. Currently, not many runtimes support it yet — even though it’s technically part of the ES6 Standard — but it’s possible support might be added in the future, so you can prepare for that by writing good code in the present.
In a nutshell, TCO (or PTC) optimizes the call stack by not opening a new frame for a function that is directly returned by another function. Instead, it reuses the same frame.
async function delay1Second() {
return delay(1000);
}
Since delay() is directly returned by delay1Second(), runtimes supporting PTC will first open a frame for delay1Second() (the outer function), but then instead of opening another frame for delay() (the inner function), it will just reuse the same frame that was opened for the outer function. This optimizes the stack because it can prevent a stack overflow (hehe) with very large recursive functions, e.g., fibonacci(5e+25). Essentially it becomes a loop, which is much faster.
PTC is only enabled when the inner function is directly returned. It’s not used when the result of the function is altered before it is returned, for example, if you had return (delay(1000) || null), or return await delay(1000).
But like I said, most runtimes and browsers don’t support PTC yet, so it probably doesn’t make a huge difference now, but it couldn’t hurt to future-proof your code.
Read more in this question: Node.js: Are there optimizations for tail calls in async functions?
Noticeable difference: Promise rejection gets handled at different places
return somePromise will pass somePromise to the call site, and await somePromise to settle at call site (if there is any). Therefore, if somePromise is rejected, it will not be handled by the local catch block, but the call site's catch block.
async function foo () {
try {
return Promise.reject();
} catch (e) {
console.log('IN');
}
}
(async function main () {
try {
let a = await foo();
} catch (e) {
console.log('OUT');
}
})();
// 'OUT'
return await somePromise will first await somePromise to settle locally. Therefore, the value or Exception will first be handled locally. => Local catch block will be executed if somePromise is rejected.
async function foo () {
try {
return await Promise.reject();
} catch (e) {
console.log('IN');
}
}
(async function main () {
try {
let a = await foo();
} catch (e) {
console.log('OUT');
}
})();
// 'IN'
Reason: return await Promise awaits both locally and outside, return Promise awaits only outside
Detailed Steps:
return Promise
async function delay1Second() {
return delay(1000);
}
call delay1Second();
const result = await delay1Second();
Inside delay1Second(), function delay(1000) returns a promise immediately with [[PromiseStatus]]: 'pending. Let's call it delayPromise.
async function delay1Second() {
return delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
Async functions will wrap their return value inside Promise.resolve()(Source). Because delay1Second is an async function, we have:
const result = await Promise.resolve(delayPromise);
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
Promise.resolve(delayPromise) returns delayPromise without doing anything because the input is already a promise (see MDN Promise.resolve):
const result = await delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
await waits until the delayPromise is settled.
IF delayPromise is fulfilled with PromiseValue=1:
const result = 1;
ELSE is delayPromise is rejected:
// jump to catch block if there is any
return await Promise
async function delay1Second() {
return await delay(1000);
}
call delay1Second();
const result = await delay1Second();
Inside delay1Second(), function delay(1000) returns a promise immediately with [[PromiseStatus]]: 'pending. Let's call it delayPromise.
async function delay1Second() {
return await delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
Local await will wait until delayPromise gets settled.
Case 1: delayPromise is fulfilled with PromiseValue=1:
async function delay1Second() {
return 1;
}
const result = await Promise.resolve(1); // let's call it "newPromise"
const result = await newPromise;
// newPromise.[[PromiseStatus]]: 'resolved'
// newPromise.[[PromiseValue]]: 1
const result = 1;
Case 2: delayPromise is rejected:
// jump to catch block inside `delay1Second` if there is any
// let's say a value -1 is returned in the end
const result = await Promise.resolve(-1); // call it newPromise
const result = await newPromise;
// newPromise.[[PromiseStatus]]: 'resolved'
// newPromise.[[PromiseValue]]: -1
const result = -1;
Glossary:
Settle: Promise.[[PromiseStatus]] changes from pending to resolved or rejected
This is a hard question to answer, because it depends in practice on how your transpiler (probably babel) actually renders async/await. The things that are clear regardless:
Both implementations should behave the same, though the first implementation may have one less Promise in the chain.
Especially if you drop the unnecessary await, the second version would not require any extra code from the transpiler, while the first one does.
So from a code performance and debugging perspective, the second version is preferable, though only very slightly so, while the first version has a slight legibility benefit, in that it clearly indicates that it returns a promise.
In our project, we decided to always use 'return await'.
The argument is that "the risk of forgetting to add the 'await' when later on a try-catch block is put around the return expression justifies having the redundant 'await' now."
Here is a typescript example that you can run and convince yourself that you need that "return await"
async function test() {
try {
return await throwErr(); // this is correct
// return throwErr(); // this will prevent inner catch to ever to be reached
}
catch (err) {
console.log("inner catch is reached")
return
}
}
const throwErr = async () => {
throw("Fake error")
}
void test().then(() => {
console.log("done")
}).catch(e => {
console.log("outer catch is reached")
});
here i leave some code practical for you can undertand it the diferrence
let x = async function () {
return new Promise((res, rej) => {
setTimeout(async function () {
console.log("finished 1");
return await new Promise((resolve, reject) => { // delete the return and you will see the difference
setTimeout(function () {
resolve("woo2");
console.log("finished 2");
}, 5000);
});
res("woo1");
}, 3000);
});
};
(async function () {
var counter = 0;
const a = setInterval(function () { // counter for every second, this is just to see the precision and understand the code
if (counter == 7) {
clearInterval(a);
}
console.log(counter);
counter = counter + 1;
}, 1000);
console.time("time1");
console.log("hello i starting first of all");
await x();
console.log("more code...");
console.timeEnd("time1");
})();
the function "x" just is a function async than it have other fucn
if will delete the return it print "more code..."
the variable x is just an asynchronous function that in turn has another asynchronous function, in the main of the code we invoke a wait to call the function of the variable x, when it completes it follows the sequence of the code, that would be normal for "async / await ", but inside the x function there is another asynchronous function, and this returns a promise or returns a" promise "it will stay inside the x function, forgetting the main code, that is, it will not print the" console.log ("more code .. "), on the other hand if we put" await "it will wait for every function that completes and finally follows the normal sequence of the main code.
below the "console.log (" finished 1 "delete the" return ", you will see the behavior.
Given the code samples below, is there any difference in behavior, and, if so, what are those differences?
return await promise
async function delay1Second() {
return (await delay(1000));
}
return promise
async function delay1Second() {
return delay(1000);
}
As I understand it, the first would have error-handling within the async function, and errors would bubble out of the async function's Promise. However, the second would require one less tick. Is this correct?
This snippet is just a common function to return a Promise for reference.
function delay(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
Most of the time, there is no observable difference between return and return await. Both versions of delay1Second have the exact same observable behavior (but depending on the implementation, the return await version might use slightly more memory because an intermediate Promise object might be created).
However, as #PitaJ pointed out, there is one case where there is a difference: if the return or return await is nested in a try-catch block. Consider this example
async function rejectionWithReturnAwait () {
try {
return await Promise.reject(new Error())
} catch (e) {
return 'Saved!'
}
}
async function rejectionWithReturn () {
try {
return Promise.reject(new Error())
} catch (e) {
return 'Saved!'
}
}
In the first version, the async function awaits the rejected promise before returning its result, which causes the rejection to be turned into an exception and the catch clause to be reached; the function will thus return a promise resolving to the string "Saved!".
The second version of the function, however, does return the rejected promise directly without awaiting it within the async function, which means that the catch case is not called and the caller gets the rejection instead.
As other answers mentioned, there is likely a slight performance benefit when letting the promise bubble up by returning it directly — simply because you don’t have to await the result first and then wrap it with another promise again. However, no one has talked about tail call optimization yet.
Tail call optimization, or “proper tail calls”, is a technique that the interpreter uses to optimize the call stack. Currently, not many runtimes support it yet — even though it’s technically part of the ES6 Standard — but it’s possible support might be added in the future, so you can prepare for that by writing good code in the present.
In a nutshell, TCO (or PTC) optimizes the call stack by not opening a new frame for a function that is directly returned by another function. Instead, it reuses the same frame.
async function delay1Second() {
return delay(1000);
}
Since delay() is directly returned by delay1Second(), runtimes supporting PTC will first open a frame for delay1Second() (the outer function), but then instead of opening another frame for delay() (the inner function), it will just reuse the same frame that was opened for the outer function. This optimizes the stack because it can prevent a stack overflow (hehe) with very large recursive functions, e.g., fibonacci(5e+25). Essentially it becomes a loop, which is much faster.
PTC is only enabled when the inner function is directly returned. It’s not used when the result of the function is altered before it is returned, for example, if you had return (delay(1000) || null), or return await delay(1000).
But like I said, most runtimes and browsers don’t support PTC yet, so it probably doesn’t make a huge difference now, but it couldn’t hurt to future-proof your code.
Read more in this question: Node.js: Are there optimizations for tail calls in async functions?
Noticeable difference: Promise rejection gets handled at different places
return somePromise will pass somePromise to the call site, and await somePromise to settle at call site (if there is any). Therefore, if somePromise is rejected, it will not be handled by the local catch block, but the call site's catch block.
async function foo () {
try {
return Promise.reject();
} catch (e) {
console.log('IN');
}
}
(async function main () {
try {
let a = await foo();
} catch (e) {
console.log('OUT');
}
})();
// 'OUT'
return await somePromise will first await somePromise to settle locally. Therefore, the value or Exception will first be handled locally. => Local catch block will be executed if somePromise is rejected.
async function foo () {
try {
return await Promise.reject();
} catch (e) {
console.log('IN');
}
}
(async function main () {
try {
let a = await foo();
} catch (e) {
console.log('OUT');
}
})();
// 'IN'
Reason: return await Promise awaits both locally and outside, return Promise awaits only outside
Detailed Steps:
return Promise
async function delay1Second() {
return delay(1000);
}
call delay1Second();
const result = await delay1Second();
Inside delay1Second(), function delay(1000) returns a promise immediately with [[PromiseStatus]]: 'pending. Let's call it delayPromise.
async function delay1Second() {
return delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
Async functions will wrap their return value inside Promise.resolve()(Source). Because delay1Second is an async function, we have:
const result = await Promise.resolve(delayPromise);
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
Promise.resolve(delayPromise) returns delayPromise without doing anything because the input is already a promise (see MDN Promise.resolve):
const result = await delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
await waits until the delayPromise is settled.
IF delayPromise is fulfilled with PromiseValue=1:
const result = 1;
ELSE is delayPromise is rejected:
// jump to catch block if there is any
return await Promise
async function delay1Second() {
return await delay(1000);
}
call delay1Second();
const result = await delay1Second();
Inside delay1Second(), function delay(1000) returns a promise immediately with [[PromiseStatus]]: 'pending. Let's call it delayPromise.
async function delay1Second() {
return await delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
Local await will wait until delayPromise gets settled.
Case 1: delayPromise is fulfilled with PromiseValue=1:
async function delay1Second() {
return 1;
}
const result = await Promise.resolve(1); // let's call it "newPromise"
const result = await newPromise;
// newPromise.[[PromiseStatus]]: 'resolved'
// newPromise.[[PromiseValue]]: 1
const result = 1;
Case 2: delayPromise is rejected:
// jump to catch block inside `delay1Second` if there is any
// let's say a value -1 is returned in the end
const result = await Promise.resolve(-1); // call it newPromise
const result = await newPromise;
// newPromise.[[PromiseStatus]]: 'resolved'
// newPromise.[[PromiseValue]]: -1
const result = -1;
Glossary:
Settle: Promise.[[PromiseStatus]] changes from pending to resolved or rejected
This is a hard question to answer, because it depends in practice on how your transpiler (probably babel) actually renders async/await. The things that are clear regardless:
Both implementations should behave the same, though the first implementation may have one less Promise in the chain.
Especially if you drop the unnecessary await, the second version would not require any extra code from the transpiler, while the first one does.
So from a code performance and debugging perspective, the second version is preferable, though only very slightly so, while the first version has a slight legibility benefit, in that it clearly indicates that it returns a promise.
In our project, we decided to always use 'return await'.
The argument is that "the risk of forgetting to add the 'await' when later on a try-catch block is put around the return expression justifies having the redundant 'await' now."
Here is a typescript example that you can run and convince yourself that you need that "return await"
async function test() {
try {
return await throwErr(); // this is correct
// return throwErr(); // this will prevent inner catch to ever to be reached
}
catch (err) {
console.log("inner catch is reached")
return
}
}
const throwErr = async () => {
throw("Fake error")
}
void test().then(() => {
console.log("done")
}).catch(e => {
console.log("outer catch is reached")
});
here i leave some code practical for you can undertand it the diferrence
let x = async function () {
return new Promise((res, rej) => {
setTimeout(async function () {
console.log("finished 1");
return await new Promise((resolve, reject) => { // delete the return and you will see the difference
setTimeout(function () {
resolve("woo2");
console.log("finished 2");
}, 5000);
});
res("woo1");
}, 3000);
});
};
(async function () {
var counter = 0;
const a = setInterval(function () { // counter for every second, this is just to see the precision and understand the code
if (counter == 7) {
clearInterval(a);
}
console.log(counter);
counter = counter + 1;
}, 1000);
console.time("time1");
console.log("hello i starting first of all");
await x();
console.log("more code...");
console.timeEnd("time1");
})();
the function "x" just is a function async than it have other fucn
if will delete the return it print "more code..."
the variable x is just an asynchronous function that in turn has another asynchronous function, in the main of the code we invoke a wait to call the function of the variable x, when it completes it follows the sequence of the code, that would be normal for "async / await ", but inside the x function there is another asynchronous function, and this returns a promise or returns a" promise "it will stay inside the x function, forgetting the main code, that is, it will not print the" console.log ("more code .. "), on the other hand if we put" await "it will wait for every function that completes and finally follows the normal sequence of the main code.
below the "console.log (" finished 1 "delete the" return ", you will see the behavior.
Looks nobody on internet describes similar problem, and similar solution is not working for me.
I am trying to scrape webpage so I created class for parser and one of the method looks like follows:
get chListUrl() {
return "http://www.teleman.pl/program-tv/stacje";
}
getChannels() {
var dict = {};
axios.get(this.chListUrl).then(function (response) {
var $ = cheerio.load(response.data);
var ile_jest_stacji = $('#stations-index a').length;
$('#stations-index a').each( (i,elem) => {
let href = $(elem).attr('href');
let kodstacji = href.replace(/\/program-tv\/stacje\//ig,'');
let nazwastacji = $(elem).text();
dict[nazwastacji]=kodstacji;
});
return dict;
}).catch(function (error) {
console.log(error);
return null;
}).finally(function() {
console.log("Koniec");
});
}
And problem is getChannels must be indirectly asynchronous because it contains axios BUT
let tm = new TM();
var a = tm.getChannels();
a is always undefined and it should be dictionary! Such construct means "assing to variable a result of execution of tm.getChannels()" so assignment should always be done AFTER whole function ends. Otherwise such syntax in language is useless because you will never be sure what value is stored in variable, and such errors are difficult to find and debug.
var a = await tm.getChannels();
NOT WORKING -> SyntaxError: await is only valid in async function (huh?)
adding async to getChannels() changes nothing.
Assing async to getChannels() and remove 'await' from assignment returns Promise{undefined} (huh?)
putting async before axios changes nothing as response is already handled by .then()
changing return dict to return await dict gives another "await is only valid in async function" (huh? axios is asynchronous)
I'm scratching my head over this for 2 weeks.
In Swift when something is return in completion handler it is assigned to variable in proper moment, why something returned by Promise not works the same way?
You need to be inside an async function to use the await expression:
The await operator is used to wait for a Promise. It can only be used inside an async function.
await operator on MDN
Example sourced from MDN:
async function f1() {
var x = await resolveAfter2Seconds(10);
console.log(x); // 10
}
f1();
Fixing your issue
class TM {
get chListUrl() {
return "http://www.teleman.pl/program-tv/stacje";
}
async getChannels() { // you need not use get syntax
let dict = {};
try { // we will be handling possible errors with try catch instead of reject
const response = await axios.get(this.chListUrl);
let $ = cheerio.load(response.data);
let ile_jest_stacji = $('#stations-index a').length;
$('#stations-index a').each( (i,elem) => {
let href = $(elem).attr('href');
let kodstacji = href.replace(/\/program-tv\/stacje\//ig,'');
let nazwastacji = $(elem).text();
dict[nazwastacji]=kodstacji;
});
return dict;
} catch(ex) {
console.log(ex);
return null
}
}
}
// let's make it work!
(async function() {
const tm = new TM()
const channels = await tm.getChannels()
// do whatever you want with channels
console.log(channels)
})()
Now, you're probably not going to call getChannels out of nowhere like this instead you will probably be inside a function that you yourself defined, you need to add the async keyword to this function. Whatever block function your code is in needs to be async.
If you want to use the async/await syntax you should remove the .then() syntax and you can resolve that way:
async getChannels() {
const response = await axios.get(this.chListUrl);
return response
}
You can learn more about async/await in the link: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
The code below gives me the following error:
SyntaxError: await is only valid in async function
async function getLastTransaction()
{
paymentsApi.listPayments(locationId, opts).then(function(transactions)
{
if(transactions[transactions.length-1] === undefined)
return; //no new transaction yet
var last_transaction_id = transactions[transactions.length-1].id;
var last_transaction_in_queue;
try {
last_transaction_in_queue = JSON.parse(order_queue[0]).order_id;
} catch (e) {
last_transaction_in_queue = order_queue[0].order_id;
}
//check if latest transaction is the same as lastest transaction in queue
if(last_transaction_id !== last_transaction_in_queue) {
console.log(`new payment...`);
var obj = await createTransactionObject(transactions[transactions.length-1], () => {
order_queue.unshift(obj);
console.log('new added', order_queue);
});
}
I don't understand the error since I'm using await for the same function createTransactionObject() but in another piece of code.
For example, in the following code, I don't get the error, and still, I'm using await before createTransactionObject()
async function populateQueue(transaction_list) {
for(var i = 0; i < transaction_list.length; i++)
{
var transaction_json = await createTransactionObject(transaction_list[i], () => {});
order_queue.unshift(transaction_json);
} }
You need to change this line:
paymentsApi.listPayments(locationId, opts).then(function(transactions)
to this:
paymentsApi.listPayments(locationId, opts).then(async (transactions) =>
The anonymous function you supply to .then needs to be asynced, because you're using await in it.
You could also replace the line with this (maybe even better):
const transactions = await paymentsApi.listPayments(locationId, opts);
Because the getLastTransaction function is asynced.
First of all you get the error not because the getLastTransaction function is async but because the anonymous function .then(function(transactions) is not async and you use await inside of it. You can't do that.
Now note that simple redeclaring the function as async function(transactions) will be syntactically correct but will that work fine? What happens now is that getLastTransaction fires some async process in the background and never awaits the result. Is that what you want?
To fix that you have to ask yourself: what am I trying to achieve? Should getLastTransaction wait for whatever the inner function is doing? Then make use of that async declaration:
async function getLastTransaction() {
const transactions = await paymentsApi.listPayments(locationId, opts);
// Some other code here
return xyz;
}
This is under the assumption that the paymentsApi is async/await compatible. If it is not then you have to play with manually creating and returning Promise objects (in which case async declaration won't help much).
paymentsApi.listPayments(locationId, opts).then(function(transactions) should be
paymentsApi.listPayments(locationId, opts).then(async function(transactions) as await can only be used in an asynchronous function.
Better still, since you already have an async function at the top level, why don't you just await paymentsApi.listPayments(locationId, opts) instead of chaining it with a then?
async function getLastTransaction() {
const transactions = await paymentsApi.listPayments(locationId, opts);
// Do something with transactions
}
await keyword works when scope is having async keyword used, here .then accepts callback function that doesn't have async, so await becomes alien here.
Lets re-write your code in async-await style:
async function getLastTransaction()
{
// #1 this fixes to adopt the await style and fixes the problem
const transactions = await paymentsApi.listPayments(locationId, opts);
// your rest code goes here ...
if(last_transaction_id !== last_transaction_in_queue) {
//now do it like this, await will make sense now
const obj = await createTransactionObject(transactions[transactions.length-1]);
order_queue.unshift(obj);
}
}
Given the code samples below, is there any difference in behavior, and, if so, what are those differences?
return await promise
async function delay1Second() {
return (await delay(1000));
}
return promise
async function delay1Second() {
return delay(1000);
}
As I understand it, the first would have error-handling within the async function, and errors would bubble out of the async function's Promise. However, the second would require one less tick. Is this correct?
This snippet is just a common function to return a Promise for reference.
function delay(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
Most of the time, there is no observable difference between return and return await. Both versions of delay1Second have the exact same observable behavior (but depending on the implementation, the return await version might use slightly more memory because an intermediate Promise object might be created).
However, as #PitaJ pointed out, there is one case where there is a difference: if the return or return await is nested in a try-catch block. Consider this example
async function rejectionWithReturnAwait () {
try {
return await Promise.reject(new Error())
} catch (e) {
return 'Saved!'
}
}
async function rejectionWithReturn () {
try {
return Promise.reject(new Error())
} catch (e) {
return 'Saved!'
}
}
In the first version, the async function awaits the rejected promise before returning its result, which causes the rejection to be turned into an exception and the catch clause to be reached; the function will thus return a promise resolving to the string "Saved!".
The second version of the function, however, does return the rejected promise directly without awaiting it within the async function, which means that the catch case is not called and the caller gets the rejection instead.
As other answers mentioned, there is likely a slight performance benefit when letting the promise bubble up by returning it directly — simply because you don’t have to await the result first and then wrap it with another promise again. However, no one has talked about tail call optimization yet.
Tail call optimization, or “proper tail calls”, is a technique that the interpreter uses to optimize the call stack. Currently, not many runtimes support it yet — even though it’s technically part of the ES6 Standard — but it’s possible support might be added in the future, so you can prepare for that by writing good code in the present.
In a nutshell, TCO (or PTC) optimizes the call stack by not opening a new frame for a function that is directly returned by another function. Instead, it reuses the same frame.
async function delay1Second() {
return delay(1000);
}
Since delay() is directly returned by delay1Second(), runtimes supporting PTC will first open a frame for delay1Second() (the outer function), but then instead of opening another frame for delay() (the inner function), it will just reuse the same frame that was opened for the outer function. This optimizes the stack because it can prevent a stack overflow (hehe) with very large recursive functions, e.g., fibonacci(5e+25). Essentially it becomes a loop, which is much faster.
PTC is only enabled when the inner function is directly returned. It’s not used when the result of the function is altered before it is returned, for example, if you had return (delay(1000) || null), or return await delay(1000).
But like I said, most runtimes and browsers don’t support PTC yet, so it probably doesn’t make a huge difference now, but it couldn’t hurt to future-proof your code.
Read more in this question: Node.js: Are there optimizations for tail calls in async functions?
Noticeable difference: Promise rejection gets handled at different places
return somePromise will pass somePromise to the call site, and await somePromise to settle at call site (if there is any). Therefore, if somePromise is rejected, it will not be handled by the local catch block, but the call site's catch block.
async function foo () {
try {
return Promise.reject();
} catch (e) {
console.log('IN');
}
}
(async function main () {
try {
let a = await foo();
} catch (e) {
console.log('OUT');
}
})();
// 'OUT'
return await somePromise will first await somePromise to settle locally. Therefore, the value or Exception will first be handled locally. => Local catch block will be executed if somePromise is rejected.
async function foo () {
try {
return await Promise.reject();
} catch (e) {
console.log('IN');
}
}
(async function main () {
try {
let a = await foo();
} catch (e) {
console.log('OUT');
}
})();
// 'IN'
Reason: return await Promise awaits both locally and outside, return Promise awaits only outside
Detailed Steps:
return Promise
async function delay1Second() {
return delay(1000);
}
call delay1Second();
const result = await delay1Second();
Inside delay1Second(), function delay(1000) returns a promise immediately with [[PromiseStatus]]: 'pending. Let's call it delayPromise.
async function delay1Second() {
return delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
Async functions will wrap their return value inside Promise.resolve()(Source). Because delay1Second is an async function, we have:
const result = await Promise.resolve(delayPromise);
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
Promise.resolve(delayPromise) returns delayPromise without doing anything because the input is already a promise (see MDN Promise.resolve):
const result = await delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
await waits until the delayPromise is settled.
IF delayPromise is fulfilled with PromiseValue=1:
const result = 1;
ELSE is delayPromise is rejected:
// jump to catch block if there is any
return await Promise
async function delay1Second() {
return await delay(1000);
}
call delay1Second();
const result = await delay1Second();
Inside delay1Second(), function delay(1000) returns a promise immediately with [[PromiseStatus]]: 'pending. Let's call it delayPromise.
async function delay1Second() {
return await delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
Local await will wait until delayPromise gets settled.
Case 1: delayPromise is fulfilled with PromiseValue=1:
async function delay1Second() {
return 1;
}
const result = await Promise.resolve(1); // let's call it "newPromise"
const result = await newPromise;
// newPromise.[[PromiseStatus]]: 'resolved'
// newPromise.[[PromiseValue]]: 1
const result = 1;
Case 2: delayPromise is rejected:
// jump to catch block inside `delay1Second` if there is any
// let's say a value -1 is returned in the end
const result = await Promise.resolve(-1); // call it newPromise
const result = await newPromise;
// newPromise.[[PromiseStatus]]: 'resolved'
// newPromise.[[PromiseValue]]: -1
const result = -1;
Glossary:
Settle: Promise.[[PromiseStatus]] changes from pending to resolved or rejected
This is a hard question to answer, because it depends in practice on how your transpiler (probably babel) actually renders async/await. The things that are clear regardless:
Both implementations should behave the same, though the first implementation may have one less Promise in the chain.
Especially if you drop the unnecessary await, the second version would not require any extra code from the transpiler, while the first one does.
So from a code performance and debugging perspective, the second version is preferable, though only very slightly so, while the first version has a slight legibility benefit, in that it clearly indicates that it returns a promise.
In our project, we decided to always use 'return await'.
The argument is that "the risk of forgetting to add the 'await' when later on a try-catch block is put around the return expression justifies having the redundant 'await' now."
Here is a typescript example that you can run and convince yourself that you need that "return await"
async function test() {
try {
return await throwErr(); // this is correct
// return throwErr(); // this will prevent inner catch to ever to be reached
}
catch (err) {
console.log("inner catch is reached")
return
}
}
const throwErr = async () => {
throw("Fake error")
}
void test().then(() => {
console.log("done")
}).catch(e => {
console.log("outer catch is reached")
});
here i leave some code practical for you can undertand it the diferrence
let x = async function () {
return new Promise((res, rej) => {
setTimeout(async function () {
console.log("finished 1");
return await new Promise((resolve, reject) => { // delete the return and you will see the difference
setTimeout(function () {
resolve("woo2");
console.log("finished 2");
}, 5000);
});
res("woo1");
}, 3000);
});
};
(async function () {
var counter = 0;
const a = setInterval(function () { // counter for every second, this is just to see the precision and understand the code
if (counter == 7) {
clearInterval(a);
}
console.log(counter);
counter = counter + 1;
}, 1000);
console.time("time1");
console.log("hello i starting first of all");
await x();
console.log("more code...");
console.timeEnd("time1");
})();
the function "x" just is a function async than it have other fucn
if will delete the return it print "more code..."
the variable x is just an asynchronous function that in turn has another asynchronous function, in the main of the code we invoke a wait to call the function of the variable x, when it completes it follows the sequence of the code, that would be normal for "async / await ", but inside the x function there is another asynchronous function, and this returns a promise or returns a" promise "it will stay inside the x function, forgetting the main code, that is, it will not print the" console.log ("more code .. "), on the other hand if we put" await "it will wait for every function that completes and finally follows the normal sequence of the main code.
below the "console.log (" finished 1 "delete the" return ", you will see the behavior.