“退货等待承诺”与“退货承诺”之间的区别


105

给定下面的代码示例,行为上是否有任何差异,如果有,这些差异是什么?

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

3
是的,我编辑了我的问题,因为您误解了我的意思,但它并没有真正回答我想知道的事情。
PitaJ'8

1
@PitaJ:我相信您打算async从第二个(return promise)样本中删除。
史蒂芬·克利西

1
@PitaJ:在这种情况下,您的第二个示例将返回一个用承诺解决的承诺。有点奇怪。
Stephen Cleary

5
jakearchibald.com/2017/await-vs-return-vs-return-await是一篇不错的文章,总结了这些差异
sanchit

2
@StephenCleary,我偶然发现了这一点,首先想到的是完全一样的,在这里,用诺言解决的诺言没有意义。但是实际上,promise.then(() => nestedPromise)它将变平并“跟随” nestedPromise。有趣的是,它与C#中的嵌套任务有何不同Unwrap?附带说明一下,似乎在 await somePromise调用Promise.resolve(somePromise).then而不是只是somePromise.then有了一些有趣的语义差异。
noseratio

Answers:


151

在大多数情况下,return和之间没有可观察到的差异return await。两个版本的delay1Second可观察行为完全相同(但根据实现的不同,该return await版本可能会使用更多的内存,因为Promise可能会创建一个中间对象)。

但是,正如@PitaJ所指出的,在一种情况下存在差异:如果returnor 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!'
  }
}

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

但是,该函数的第二个版本确实直接返回了被拒绝的承诺,而没有在async函数中等待它,这意味着调用该catch案例,而是由调用者获取拒绝。


也许还提到堆栈跟踪会有所不同(即使没有try / catch)?我认为这是此示例中人们最常遇到的问题:]
Benjamin Gruenbaum

我发现在一种情况下,return new Promise(function(resolve, reject) { })for...of循环中使用,然后在a 之后resolve()在循环中调用pipe()不会暂停程序的执行,直到管道完成为止(根据需要),但是使用了await new Promise(...)。后者甚至是有效/正确的语法吗?是“简写”的return await new Promise(...)?您能帮助我理解为什么后者有效而前者无效吗?对于背景下,方案是在solution 02这个答案
user1063287

10

正如其他答案提到的那样,当通过直接返回承诺来使承诺冒泡时,可能会带来一点性能上的好处-仅仅是因为您不必先等待结果,而后再用另一个承诺包装它。但是,还没有人讨论过尾调用优化

尾部调用优化“适当的尾部调用”是解释器用来优化调用堆栈的一种技术。目前,还没有多少运行时支持它 -尽管从技术上讲它是ES6标准的一部分-但将来可能会添加支持,因此您可以通过编写良好的代码来为此做准备。

简而言之,TCO(或PTC)通过为另一个函数直接返回的函数打开新框架来优化调用堆栈。而是,它重用同一帧。

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

由于delay()是直接返回的delay1Second(),因此支持PTC的运行时将首先为delay1Second()(外部函数)打开一个框架,然后代替为(内部函数)打开另一个框架delay(),它将仅重用为外部函数打开的相同框架。这可以优化堆栈,因为它可以通过非常大的递归函数(例如)防止堆栈溢出(hehe)fibonacci(5e+25)。从本质上讲,它变成了一个循环,速度更快。

仅当直接返回内部函数时,才启用PTC 。当函数的结果在返回之前被更改时,例如,如果您有return (delay(1000) || null),或,则不使用它return await delay(1000)

但是就像我说的那样,大多数运行时和浏览器尚不支持PTC,因此它现在可能并没有太大的区别,但这不会损害代码的前瞻性。

阅读更多有关此问题的信息:Node.js:异步函数中是否对尾部调用进行了优化?


2

这是一个很难回答的问题,因为在实践中,这取决于您的编译器(可能babel)实际渲染的方式async/await。无论如何,清楚的事情是:

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

  • 特别是如果您删除了不必要的await,则第二个版本将不需要编译器提供任何额外的代码,而第一个版本则需要。

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


为什么功能表现相同?第一个返回解析值(undefined),第二个返回a Promise
阿米特

4
@Amit两个函数都返回一个Promise
PitaJ,2016年

阿克 这就是为什么我不能忍受async/await-我觉得很难推理。@PitaJ是正确的,两个函数都返回一个Promise。
nrabinowitz'8

如果我用a包围两个异步函数的主体try-catch怎么办?在这种return promise情况下,rejection不会发现任何错误,是正确的,而在这种return await promise情况下,是正确的吗?
PitaJ '16

两者都返回一个Promise,但是第一个“承诺”一个原始值,第二个“承诺”一个Promise。如果您await在某个呼叫站点上分别使用它们,结果将大不相同。
阿米特

0

在这里,我留下一些实用的代码,您可以理解它的不同之处

 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函数内部还有另一个异步函数,这将返回一个promise或返回一个“ promise”,它将保留在x函数内部,而忘记了主要代码,也就是说,它将不打印另一方面,如果使用console.log(“更多代码..”),则我们将等待每个函数完成,并最终遵循主代码的正常顺序。

在“ console.log”下面(“完成1”后删除“ return”,您将看到该行为。


1
尽管这段代码可以解决问题,但包括解释如何以及为何解决该问题的说明,确实可以帮助提高您的帖子质量,并可能导致更多的投票。请记住,您将来会为读者回答问题,而不仅仅是现在问的人。请编辑您的答案以添加说明,并指出适用的限制和假设。
布赖恩

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


0

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

  • return somePromise会将somePromise传递到呼叫站点,并将await somePromise传递到呼叫站点(如果有)。因此,如果somePromise被拒绝,它将不会由本地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拒绝,将执行本地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。叫它delayPromise
async function delay1Second() {
  return delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
  1. 异步函数会将其返回值包装在Promise.resolve()Source)中。因为delay1Second是异步函数,所以我们有:
const result = await Promise.resolve(delayPromise); 
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
  1. Promise.resolve(delayPromise)delayPromise不做任何事情就返回,因为输入已经是一个承诺(请参见MDN Promise.resolve):
const result = await delayPromise; 
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
  1. await等待,直到delayPromise结算。
  • 如果delayPromise满足PromiseValue = 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。叫它delayPromise
async function delay1Second() {
  return await delayPromise;
// delayPromise.[[PromiseStatus]]: 'pending'
// delayPromise.[[PromiseValue]]: undefined
}
  1. 当地的等待将等到delayPromise解决。
  • 情况1delayPromise以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; 
  • 情况2delayPromise被拒绝:
// 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]]从更改pendingresolvedrejected
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.