鉴于以下代码示例,行为是否有任何差异,如果是,那么这些差异是什么?
return await promise
async function delay1Second() {
return (await delay(1000));
}
return promise
async function delay1Second() {
return delay(1000);
}
据我了解,第一个会在异步函数中进行错误处理,并且错误会从异步函数的Promise中冒出来。但是,第二个需要少一个滴答。这是对的吗?
此代码段只是返回Promise以供参考的常用函数。
function delay(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
答案 0 :(得分:86)
大多数情况下,return
和return await
之间没有可观察到的差异。 delay1Second
的两个版本都具有完全相同的可观察行为(但根据实现,return await
版本可能会使用稍多的内存,因为可能会创建一个中间Promise
对象。
然而,正如@PitaJ指出的那样,有一种情况存在差异:如果return
或return await
嵌套在try
- catch
块中。考虑这个例子
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!'
}
}
在第一个版本中,async函数在返回结果之前等待被拒绝的promise,这会导致拒绝被转换为异常并且要到达catch
子句;因此,该函数将返回一个解析为字符串的承诺"已保存!"。
然而,该函数的第二个版本确实直接返回被拒绝的承诺而不在异步函数中等待它,这意味着catch
情况不是调用,然后调用者获得拒绝。
答案 1 :(得分:3)
<table>
会将 somePromise 传递给呼叫站点,并且return somePromise
somePromise 传递到呼叫站点( (如果有)。因此,如果somePromise被拒绝,它将不会由本地catch块处理,而是由呼叫站点的catch块处理。
await
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'
将首先等待 somePromise 在本地定居。因此,值或Exception首先将在本地处理。 =>如果return await somePromise
被拒绝,将执行本地catch块。
somePromise
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'
在本地和外部等待,return await Promise
仅在外部等待return Promise
async function delay1Second() {
return delay(1000);
}
; delay1Second()
const result = await delay1Second();
内,函数delay1Second()
使用delay(1000)
立即返回一个承诺。我们称之为[[PromiseStatus]]: 'pending
。delayPromise
async function delay1Second() {
return delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
(Source)中。因为Promise.resolve()
是一个异步函数,所以我们有:delay1Second
const result = await Promise.resolve(delayPromise);
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
不执行任何操作就返回Promise.resolve(delayPromise)
,因为输入已经是一个承诺(请参见MDN Promise.resolve):delayPromise
const result = await delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
等待await
结算。delayPromise
满足PromiseValue = 1:delayPromise
const result = 1;
被拒绝:delayPromise
// jump to catch block if there is any
async function delay1Second() {
return await delay(1000);
}
; delay1Second()
const result = await delay1Second();
内,函数delay1Second()
使用delay(1000)
立即返回一个承诺。我们称之为[[PromiseStatus]]: 'pending
。delayPromise
async function delay1Second() {
return await delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
结算。delayPromise
满足PromiseValue = 1:delayPromise
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;
被拒绝:delayPromise
// 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;
从Promise.[[PromiseStatus]]
变为pending
或resolved
答案 2 :(得分:2)
这是一个很难回答的问题,因为它实际上取决于您的转换程序(可能是babel
)实际呈现async/await
的方式。无论如何都清楚:
两个实现都应该表现相同,但第一个实现可能在链中只有一个Promise
。
特别是如果删除不必要的await
,第二个版本将不需要来自转换器的任何额外代码,而第一个版本则需要。
因此,从代码性能和调试的角度来看,第二个版本更可取,虽然只是非常轻微,但第一个版本具有轻微的易读性,因为它清楚地表明它返回了一个承诺。
答案 3 :(得分:2)
如其他答案所述,通过直接返回承诺而使承诺冒泡时,可能会带来一点性能上的好处-仅仅是因为您不必先等待结果,然后再将其与另一个承诺包装在一起。但是,还没有人谈论尾部呼叫优化。
Tail call optimization或“proper tail calls”是解释器用来优化调用堆栈的一种技术。目前,not many runtimes support it yet(尽管从技术上讲是ES6 Standard的组成部分),但将来可能会添加支持,因此您可以通过编写良好的代码来为此做好准备。
简而言之,TCO(或PTC)通过不为该函数打开新框架来优化调用堆栈,该框架直接由另一个函数返回。相反,它会重用同一帧。async function delay1Second() {
return delay(1000);
}
由于delay()
由delay1Second()
直接返回,因此支持PTC的运行时将首先为delay1Second()
(外部函数)打开一个框架,但随后不打开另一个 delay()
的框架(内部函数),它将仅重复使用为外部函数打开的框架。这可以优化堆栈,因为它可以通过非常大的递归函数(例如fibonacci(5e+25)
)来防止 堆栈溢出 (呵呵)。从本质上讲,它变成了一个循环,速度更快。
仅当直接返回内部函数 时才启用PTC。在返回函数之前更改函数的结果时,例如,如果您有return (delay(1000) || null)
或return await delay(1000)
,则不会使用它。
但是,就像我说的那样,大多数运行时和浏览器尚不支持PTC,因此它现在可能并没有太大的区别,但这不会损害您的代码过时。
进一步了解此问题:Node.js: Are there optimizations for tail calls in async functions?
答案 4 :(得分:0)
在这里我留下一些实用的代码,您可以理解它的区别
for
函数“ x”只是一个异步函数,它具有其他功能 如果将删除返回的信息,则打印“更多代码...”
变量x只是一个异步函数,又具有另一个异步函数,在代码的主体中,我们调用等待操作以调用变量x的函数,完成后,它遵循代码的顺序,即对于“ async / await”来说是正常的,但是在x函数内部还有另一个异步函数,这将返回一个promise或返回一个“ promise”,它将保留在x函数内部,而忘记了主要代码,也就是说,它将不打印“ console.log”(“更多代码..”),另一方面,如果我们输入“ await”,它将等待每个功能完成,并最终遵循主代码的正常顺序。
在“ console.log”下面(“完成1”后删除“ return”,您将看到该行为。
答案 5 :(得分:0)
这是一个打字稿示例,您可以运行它并说服自己需要“返回等待”
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")
});