“等待承诺的回报”与“承诺的回报”的区别

给出下面的代码示例,在行为上是否存在差异,如果存在,那么这些差异是什么?

return await promise

async function delay1Second() {
return (await delay(1000));
}

return promise

async function delay1Second() {
return delay(1000);
}

据我所知,第一个函数在异步函数中具有错误处理功能,错误会从异步函数的承诺中冒出来。然而,第二种方法需要少一个滴答声。是这样吗?

这个代码片段只是一个返回一个承诺作为参考的常用函数。

function delay(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
63531 次浏览

这是一个很难回答的问题,因为它实际上取决于您的传送器(可能是 babel)实际上如何呈现 async/await。无论如何,有些事情是清楚的:

  • 两个实现的行为应该相同,尽管第一个实现 在链中少了一个 Promise

  • 特别是如果您丢弃了不必要的 await,第二个版本将不需要任何额外的代码从传送器,而第一个做。

因此,从代码性能和调试的角度来看,第二个版本更可取,尽管只是略有可取,而第一个版本具有轻微的易读性优势,因为它清楚地表明它返回了一个承诺。

大多数情况下,returnreturn await之间没有明显的差异。两个版本的 delay1Second具有完全相同的可观察行为(但是取决于实现,return await版本可能会使用稍微多一点的内存,因为可能会创建一个中间的 Promise对象)。

然而,正如@PitaJ 所指出的,有一种情况是有区别的: 如果 returnreturn 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!'
}
}

在第一个版本中,异步函数在返回结果之前等待被拒绝的承诺,这将导致拒绝变成一个异常并且到达 catch子句; 因此,该函数将返回一个解析为字符串“ Saved!”的承诺.

然而,该函数的第二个版本确实直接返回被拒绝的承诺 而不必在异步函数中等待它,这意味着调用 catch的情况是 没有,而调用者得到拒绝。

正如其他答案所提到的,直接返回承诺可能会带来轻微的性能收益,这仅仅是因为您不必先等待结果,然后再用另一个承诺包装它。然而,目前还没有人谈到 尾部呼叫优化尾部呼叫优化

尾部调用优化 “正确的尾随召唤”是解释器用来优化调用堆栈的一种技术。目前,还没有多少运行时支持它ーー尽管它在技术上是 ES6标准的一部分ーー但将来可能会增加对它的支持,因此您可以通过目前编写好的代码来为此做准备。

简而言之,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,所以现在它可能不会产生很大的差异,但是它不会损害您的代码的未来性。

阅读更多这个问题: Js: 是否对异步函数中的尾部调用进行了优化?

在这里我留下了一些实用的代码,你可以理解其中的区别

 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");
})();

函数“ x”只是一个异步函数而不是其他函数 如果将删除返回它打印“更多代码..

变量 x 只是一个异步函数,它有另一个异步函数,在代码的主代码中,我们调用一个等待来调用变量 x 的函数,当它完成时,它遵循代码的顺序,这对于异步/等待是正常的,但是在 x 函数中有另一个异步函数,它返回一个承诺,或者返回一个“承诺”,它将停留在 x 函数中,忘记主代码,也就是说,它不会打印“ console.log”(更多代码)。. ")另一方面,如果我们放置“ wait”,它将等待每个完成并最终遵循主代码的正常顺序的函数。

在“ consol.log (”完成1“删除”返回“下面,您将看到行为。

下面是一个打印脚本示例,您可以运行该示例并说服自己需要“ return waiting”

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")
});

明显的区别: 承诺拒绝在不同的地方得到处理

  • return somePromise 一些承诺传递到呼叫站点,await 一些承诺在呼叫站点(如果有的话)结算。因此,如果有些承诺被拒绝,它将不会由本地 catch 块处理,而是由调用站点的 catch 块处理。

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 将首先等待 一些承诺在本地结算。因此,值或 Exception 将首先在本地处理。如果拒绝 somePromise,将执行 Local catch 块。

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);
}
  1. 呼叫 delay1Second();
const result = await delay1Second();
  1. delay1Second()中,函数 delay(1000)[[PromiseStatus]]: 'pending一起立即返回一个承诺。
async function delay1Second() {
return delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
  1. 异步函数将返回值封装在 Promise.resolve()(来源)中。因为 delay1Second是一个异步函数,我们有:
const result = await Promise.resolve(delayPromise);
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
  1. Promise.resolve(delayPromise)返回 delayPromise而不执行任何操作,因为输入已经是一个承诺(参见 承诺,决心) :
const result = await delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
  1. await等待,直到 delayPromise被解决。
  • 如果 delayPromise的允诺值为1:
const result = 1;
  • ELSE 是 delayPromise被拒绝:
// jump to catch block if there is any

回来等待承诺

async function delay1Second() {
return await delay(1000);
}
  1. 呼叫 delay1Second();
const result = await delay1Second();
  1. delay1Second()中,函数 delay(1000)[[PromiseStatus]]: 'pending一起立即返回一个承诺。
async function delay1Second() {
return await delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
  1. 本地等待将等待,直到 delayPromise得到解决。
  • 案例1 : delayPromise使用  诺言值 = 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;
  • 案例2 : 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变为 resolvedrejected

在我们的项目中,我们决定始终使用“返回等待”。 其论点是“在返回表达式周围放置 try-catch 块时,由于存在忘记添加‘ wait’的风险,因此现在使用冗余的‘ wait’是合理的。”