【问题标题】:Difference between `return await promise` and `return promise``return await promise`和`return promise`之间的区别
【发布时间】:2016-12-07 02:33:33
【问题描述】:

鉴于下面的代码示例,在行为上有什么不同,如果有,有什么不同?

return await promise

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

return promise

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

据我了解,第一个将在异步函数中进行错误处理,并且错误会从异步函数的 Promise 中冒出来。但是,第二个需要少一个刻度。这是正确的吗?

这个sn-p只是一个普通函数,返回一个Promise供参考。

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

【问题讨论】:

  • 是的,我编辑了我的问题,因为你误解了我的意思,它并没有真正回答我的疑惑。
  • @PitaJ:我相信您的意思是从您的第二个 (return promise) 样本中删除 async
  • @StephenCleary 不。我是为了这个。想象一下在返回之前还有其他等待调用等。
  • jakearchibald.com/2017/await-vs-return-vs-return-await 是一篇总结差异的好文章
  • @StephenCleary,我偶然发现了这一点,首先想到的完全一样,用承诺解决的承诺在这里没有意义。但随着事情的发展,promise.then(() => nestedPromise) 会变平并“跟随”nestedPromise。有趣的是,它与 C# 中的嵌套任务有何不同,我们必须 Unwrap 它。附带说明一下,it appears thatawait somePromise 调用Promise.resolve(somePromise).then,而不仅仅是somePromise.then,有一些有趣的语义差异。

标签: javascript async-await


【解决方案1】:

大多数时候,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!'
  }
}

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

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

【讨论】:

  • 也许还提到堆栈跟踪会有所不同(即使没有 try/catch)?我认为这是人们在这个例子中最常遇到的问题:]
  • 我发现在一种情况下,在for...of 循环中使用return new Promise(function(resolve, reject) { }),然后在pipe() 之后在循环中调用resolve() 不会暂停程序执行,直到管道完成,根据需要,但是使用await new Promise(...) 可以。后者甚至是有效/正确的语法吗?是return await new Promise(...) 的“简写”吗?你能帮我理解为什么后者有效而前者无效吗?对于上下文,场景位于solution 02 of this answer
【解决方案2】:

正如其他答案所提到的,通过直接返回 Promise 让 Promise 冒泡时,可能会有轻微的性能优势——仅仅是因为您不必先等待结果,然后再用另一个 Promise 包装它。但是,还没有人谈论过尾调用优化

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()(外部函数)打开一个框架,而不是为@987654330 打开一个另一个 框架@(内部函数),它只会重用为外部函数打开的同一帧。这优化了堆栈,因为它可以防止 堆栈溢出(呵呵)具有非常大的递归函数,例如 fibonacci(5e+25)。本质上它变成了一个循环,速度要快得多。

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

但就像我说的那样,大多数运行时和浏览器还不支持 PTC,所以现在它可能不会产生太大的影响,但它不会对你的代码进行未来的验证。

阅读此问题的更多信息:Node.js: Are there optimizations for tail calls in async functions?

【讨论】:

    【解决方案3】:

    明显的区别:Promise 拒绝在不同的地方处理

    • 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 将首先等待 somePromise 在本地定居。因此,值或异常将首先在本地处理。 => 如果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 的promise。我们就叫它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 is 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 的promise。我们称之为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]]pending 更改为 resolvedrejected

    【讨论】:

    • 解释得很漂亮! Promise 的逐步包装和解包使差异变得一目了然。其中一个重要的收获是 Promise.resolve 在传递 Promise 时返回的值。我最初以为它会返回一个已解决的承诺,但不,它按原样返回承诺。
    【解决方案4】:

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

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

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

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

    【讨论】:

    • 为什么函数的行为相同?第一个返回解析值 (undefined),第二个返回 Promise
    • @Amit 两个函数都返回一个 Promise
    • 确认。这就是我无法忍受async/await 的原因——我觉得这更难推理。 @PitaJ 是正确的,两个函数都返回一个 Promise。
    • 如果我用try-catch 包围两个异步函数的主体会怎样?在return promise 的情况下,任何rejection 都不会被捕获,对,而在return await promise 的情况下,它会是,对吧?
    • 两者都返回一个 Promise,但第一个“承诺”一个原始值,第二个“承诺”一个 Promise。如果您在某个呼叫站点上await每一个,结果将非常不同。
    【解决方案5】:

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

     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 的函数,当它完成时,它遵循代码的顺序,即对于“async / await”是正常的,但是在x函数内部还有另一个异步函数,这会返回一个promise或返回一个“promise”,它会留在x函数内部,忘记主代码,也就是说,它会不打印"console.log("more code .."),另一方面,如果我们输入"await",它将等待每个函数完成并最终遵循主代码的正常顺序。

    下面的“console.log(”finished 1”删除“return”,你会看到行为。

    【讨论】:

    • 虽然此代码可以解决问题,including an explanation 说明如何以及为什么解决问题将真正有助于提高您的帖子质量,并可能导致更多的赞成票。请记住,您正在为将来的读者回答问题,而不仅仅是现在提出问题的人。请edit您的答案添加解释并说明适用的限制和假设。
    【解决方案6】:

    这是一个打字稿示例,您可以运行并说服自己需要“返回等待”

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

    【讨论】:

      【解决方案7】:

      在我们的项目中,我们决定始终使用“return await”。 其论点是“在稍后将 try-catch 块放在 return 表达式周围时忘记添加 'await' 的风险证明现在有多余的 'await' 是合理的。”

      【讨论】:

        猜你喜欢
        • 2017-05-17
        • 2013-06-09
        • 2021-08-25
        • 2019-11-13
        • 1970-01-01
        • 2013-06-02
        相关资源
        最近更新 更多