【问题标题】:Difference between async/await and ES6 yield with generatorsasync/await 和 ES6 生成器之间的区别
【发布时间】:2016-07-11 19:58:29
【问题描述】:

我刚刚阅读了这篇精彩的文章 «Generators»,它清楚地突出了这个函数,它是一个用于处理生成器函数的辅助函数:

function async(makeGenerator){
  return function () {
    var generator = makeGenerator.apply(this, arguments);

    function handle(result){
      // result => { done: [Boolean], value: [Object] }
      if (result.done) return Promise.resolve(result.value);

      return Promise.resolve(result.value).then(function (res){
        return handle(generator.next(res));
      }, function (err){
        return handle(generator.throw(err));
      });
    }

    try {
      return handle(generator.next());
    } catch (ex) {
      return Promise.reject(ex);
    }
  }
}

我假设或多或少是 async 关键字与 async/await 实现的方式。所以问题是,如果是这样的话,那么到底是什么await 关键字和 yield 关键字之间的区别是什么? await 是否总是将某事变成承诺,而 yield 没有做出这样的保证?这是我最好的猜测!

您还可以在本文中看到 async/await 与带有生成器的 yield 的相似之处,其中他描述了“生成”函数 ES7 async functions

【问题讨论】:

  • 异步函数 -> 一个协程。生成器 -> 迭代器,它使用协程来管理其内部迭代机制。 await 挂起协程,而 yield 从某个生成器使用的协程返回结果
  • async/await 不是 ES7 的一部分。请阅读标签说明。
  • @david haim,是的,但是异步等待是建立在生成器之上的,所以它们并不明显

标签: javascript node.js ecmascript-6 generator ecmascript-next


【解决方案1】:

这个想法是递归链接then() 调用以复制await 的行为,这允许以同步方式调用async 例程。生成器函数用于将控制权(和每个值)从被调用者返回给调用者,这恰好是 _asyncToGenerator() 包装函数。

如上所述,这是 Babel 用来创建 polyfill 的技巧。我稍微编辑了代码以使其更具可读性并添加了 cmets。

(async function () {
  const foo = await 3;
  const bar = await new Promise((resolve) => resolve(7));
  const baz = bar * foo;
  console.log(baz);
})();

function _asyncToGenerator(fn) {
  return function () {
    let gen = fn(); // Start the execution of the generator function and store the generator object.
    return new Promise(function (resolve, reject) {
      function step(func, arg) {
        try {
          let item = gen[func](arg); // Retrieve the function object from the property name and invoke it. Similar to eval(`gen.${func}(arg)`) but safer. If the next() method is called on the generator object, the item value by the generator function is saved and the generator resumes execution. The value passed as an argument is assigned as a result of a yield expression.
          if (item.done) {
            resolve(item.value);
            return; // The executor return value is ignored, but we need to stop the recursion here.
          }
          // The trick is that Promise.resolve() returns a promise object that is resolved with the value given as an argument. If that value is a promise object itself, then it's simply returned as is.
          return Promise.resolve(item.value).then(
            (v) => step("next", v),
            (e) => step("throw", e)
          );
        } catch (e) {
          reject(e);
          return;
        }
      }
      return step("next");
    });
  };
}

_asyncToGenerator(function* () { // <<< Now it's a generator function.
  const foo = yield 3; // <<< Now it's yield, not await.
  const bar = yield new Promise((resolve, reject) => resolve(7)); // <<< Each item is converted to a thenable object and recursively enclosed into chained then() calls.
  const baz = bar * foo;
  console.log(baz);
})();

【讨论】:

    【解决方案2】:

    试试这个我曾经理解的测试程序await/async with promises。

    程序 #1:没有承诺它不会按顺序运行

    function functionA() {
      console.log('functionA called');
      setTimeout(function() {
        console.log('functionA timeout called');
        return 10;
      }, 15000);
    
    }
    
    function functionB(valueA) {
      console.log('functionB called');
      setTimeout(function() {
        console.log('functionB timeout called = ' + valueA);
        return 20 + valueA;
      }, 10000);
    }
    
    function functionC(valueA, valueB) {
    
      console.log('functionC called');
      setTimeout(function() {
        console.log('functionC timeout called = ' + valueA);
        return valueA + valueB;
      }, 10000);
    
    }
    
    async function executeAsyncTask() {
      const valueA = await functionA();
      const valueB = await functionB(valueA);
      return functionC(valueA, valueB);
    }
    console.log('program started');
    executeAsyncTask().then(function(response) {
      console.log('response called = ' + response);
    });
    console.log('program ended');

    计划 #2:承诺

    function functionA() {
      return new Promise((resolve, reject) => {
        console.log('functionA called');
        setTimeout(function() {
          console.log('functionA timeout called');
          // return 10;
          return resolve(10);
        }, 15000);
      });
    }
    
    function functionB(valueA) {
      return new Promise((resolve, reject) => {
        console.log('functionB called');
        setTimeout(function() {
          console.log('functionB timeout called = ' + valueA);
          return resolve(20 + valueA);
        }, 10000);
    
      });
    }
    
    function functionC(valueA, valueB) {
      return new Promise((resolve, reject) => {
        console.log('functionC called');
        setTimeout(function() {
          console.log('functionC timeout called = ' + valueA);
          return resolve(valueA + valueB);
        }, 10000);
    
      });
    }
    
    async function executeAsyncTask() {
      const valueA = await functionA();
      const valueB = await functionB(valueA);
      return functionC(valueA, valueB);
    }
    console.log('program started');
    executeAsyncTask().then(function(response) {
      console.log('response called = ' + response);
    });
    console.log('program ended');

    【讨论】:

      【解决方案3】:

      yield+gen.next()-as-a-language-feature 可用于描述(或实现)await-async 抽象出的底层控制流。 p>


      正如其他答案所暗示的那样,await-as-a-language-feature 是(或可以被认为是)yield 之上的实现。

      这是一个更直观的理解:

      假设我们在异步函数中有 42 个awaitsawait A -&gt; await B -&gt; ...

      内心深处相当于拥有yield A -&gt; tries resolve this as a Promise[1]

      -&gt; if resolvable, we yield B, and repeat [1] for B

      -&gt; if not resolveable, we throw

      所以我们最终在生成器中得到了 42 个yields。在我们的控制器中,我们只是继续执行gen.next(),直到它完成或被拒绝。 (即这与在包含 42 个 await 的异步函数上使用 await 相同。)

      这就是为什么像 redux-saga 这样的 lib 使用生成器然后将 Promise 传递到 saga 中间件以在一个地方全部解析的原因;从而将 Promise 构造与其评估解耦,从而与 Free Monad 非常相似。

      【讨论】:

        【解决方案4】:

        tl;博士

        99% 的时间使用 async/await 而不是生成器。为什么?

        1. async/await 直接替换了最常见的承诺链工作流程,允许将代码声明为好像它是同步的,大大简化了它。

        2. 生成器抽象了您将调用一系列相互依赖并最终处于“完成”状态的异步操作的用例。最简单的示例是对最终返回最后一组的结果进行分页,但您只会根据需要调用页面,而不是立即连续调用。

        3. async/await 实际上是一个建立在生成器之上的抽象,可以更轻松地处理 Promise。

        See very in-depth Explanation of Async/Await vs. Generators

        【讨论】:

          【解决方案5】:

          好吧,事实证明async/await 和生成器之间的关系非常密切。而且我相信async/await 将始终建立在生成器之上。如果你看一下 Babel 转换 async/await 的方式:

          Babel 接受这个:

          this.it('is a test', async function () {
          
              const foo = await 3;
              const bar = await new Promise(resolve => resolve('7'));
              const baz = bar * foo;
              console.log(baz);
          
          });
          

          把它变成这个

          function _asyncToGenerator(fn) {
              return function () {
                  var gen = fn.apply(this, arguments);
                  return new Promise(function (resolve, reject) {
                      function step(key, arg) {
                          try {
                              var info = gen[key](arg);
                              var value = info.value;
                          } catch (error) {
                              reject(error);
                              return;
                          }
                          if (info.done) {
                              resolve(value);
                          } else {
                              return Promise.resolve(value).then(function (value) {
                                  return step("next", value);
                              }, function (err) {
                                  return step("throw", err);
                              });
                          }
                      }
          
                      return step("next");
                  });
              };
          }
          
          
          this.it('is a test', _asyncToGenerator(function* () {   // << now it's a generator
          
              const foo = yield 3;    //  <<< now it's yield, not await
              const bar = yield new Promise(resolve => resolve(7));
              const baz = bar * foo;
              console.log(baz);
          
          }));
          

          你自己算算。

          这使得async 关键字看起来就是那个包装函数,但如果是这样的话,那么await 就会变成yield,稍后当他们成为本地人。

          你可以在这里看到更多的解释: https://www.promisejs.org/generators/

          【讨论】:

          • NodeJS 有一段时间原生 async/await,没有生成器:codeforgeek.com/2017/02/…
          • @Bram 本地实现绝对使用引擎盖下的生成器,同样的事情,只是抽象了。
          • 我不这么认为。 Async/await 在 V8 引擎中原生实现。 ES6 特性 async/await 是 ES7 的生成器。它是 V8 引擎 5.5 版本的一部分(在 Node 中使用):v8project.blogspot.nl/2016/10/v8-release-55.html。可以将 ES7 async/await 转译成 ES6 生成器,但在新版本的 NodeJS 中,这不再需要,而且 async/await 的性能甚至似乎比生成器更好:medium.com/@markherhold/…
          • async/await 使用生成器来完成它的工作
          • @AlexanderMills 你能分享一些合法的资源,说 async/await 在内部使用生成器吗?检查这个 ans stackoverflow.com/a/39384160/3933557 这与这个论点相矛盾。我认为,仅仅因为 Babel 使用了生成器,并不意味着它在底层实现类似。对此有任何想法
          【解决方案6】:

          在许多方面,生成器是 async/await 的超集。现在 async/await 的堆栈跟踪比 co 更清晰,co 是最流行的基于 async/await 的生成器库。您可以使用生成器实现自己的 async/await 风格并添加新功能,例如在 non-promise 上对 yield 的内置支持或在 RxJS observables 上构建它。

          因此,简而言之,生成器为您提供了更大的灵活性,并且基于生成器的库通常具有更多功能。但是 async/await 是语言的核心部分,它是标准化的,不会随你而变,你也不需要库来使用它。我有一个blog post,其中详细介绍了异步/等待和生成器之间的区别。

          【讨论】:

            【解决方案7】:

            await 关键字和 yield 关键字之间到底有什么区别?

            await 关键字只能用于async functions,而yield 关键字只能用于生成器function*s。这些显然也不同 - 一个返回 Promise,另一个返回生成器。

            await 是否总是将某事变成承诺,而yield 不做这样的保证?

            是的,await 将对等待的值调用 Promise.resolve

            yield 只是在生成器之外产生值。

            【讨论】:

            • 一个小问题,但正如我在回答中提到的那样,规范不使用 Promise.resolve (它以前曾经使用过),它使用 PromiseCapability::resolve 更准确地由 Promise 构造函数表示.
            • @Arnavion: Promise.resolve 与 async/await 规范直接使用的 new PromiseCapability(%Promise%) 完全相同,我只是觉得 Promise.resolve 更好理解。
            • Promise.resolve 有一个额外的“IsPromise == true?然后返回相同的值”短路,异步没有。也就是说,await p 其中p 是一个promise 将返回一个解析为p 的新promise,而Promise.resolve(p) 将返回p
            • 哦,我错过了 - 我认为这只是在 Promise.cast 中,出于一致性原因已被弃用。不过没关系,反正我们看不到这个承诺。
            • var r = await p; console.log(r); 应该被转换成类似:p.then(console.log);,而p 可能被创建为:var p = new Promise(resolve =&gt; setTimeout(resolve, 1000, 42));,所以说“等待调用”是错误的> Promise.resolve",它是一些完全远离调用Promise.resolve的'await'表达式的其他代码,因此将调用转换后的await表达式,即Promise.then(console.log)并打印出42。跨度>
            【解决方案8】:

            yield 可以被认为是await 的构建块。 yield 获取它给出的值并将其传递给调用者。然后,调用者可以使用该值 (1) 为所欲为。稍后,调用者可能会将一个值返回给生成器(通过generator.next()),该值将成为yield 表达式(2)的结果,或者似乎由yield 表达式(3)抛出的错误。

            async-await可以考虑使用yield。在 (1) 处,调用者(即 async-await 驱动程序 - 类似于您发布的函数)将使用与 new Promise(r =&gt; r(value) 类似的算法将值包装在一个承诺中(注意,不是 Promise.resolve,但这没什么大不了的)。然后它等待承诺解决。如果它满足,它会将满足的值传回 (2)。如果拒绝,则在 (3) 处将拒绝原因作为错误抛出。

            所以async-await 的效用是这种机制,它使用yield 将产生的值作为承诺解包并将其解析的值传回,重复直到函数返回其最终值。

            【讨论】:

            • 检查这个答案stackoverflow.com/a/39384160/3933557 这与这个论点相矛盾。 async-await 看起来与 yield 类似,但它在底层使用了 Promise 链。如果你有任何好的资源说“async-await 可以考虑使用 yield”,请分享。
            • 我不确定您如何认为该答案是“与此论点相矛盾”,因为它与此答案的含义相同。 >与此同时,像 Babel 这样的编译器允许你编写 async/await 并将代码转换为生成器。
            • 它说 babel 转换为生成器,但你说的是“yield 可以被认为是 await 的构建块”和“async-await 可以被认为是使用 yield.”。这对我的理解是不正确的(有待更正)。 async-await 在内部使用该答案中提到的承诺链。我想了解我是否缺少某些东西,请您分享您对此的想法。
            • 这个答案并没有声称全世界所有的 ES 引擎都在内部使用生成器实现承诺。有些可能;有些可能不会;这与这是一个答案的问题无关。尽管如此,可以使用具有特定方式驱动生成器的生成器来理解 Promise 的工作方式,这就是这个答案所解释的。
            猜你喜欢
            • 2020-09-16
            • 2012-05-04
            • 1970-01-01
            • 2021-09-20
            • 2015-01-06
            • 2018-02-21
            • 1970-01-01
            • 2015-01-14
            • 1970-01-01
            相关资源
            最近更新 更多