async/await 和 ES6 yield 与生成器的区别

Difference between async/await and ES6 yield with generators

我刚刚读了这篇精彩的文章 «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 有何相似之处,其中他描述了 'spawn' 函数 ES7 async functions

yield可以认为是await的积木。 yield 获取给定的值并将其传递给调用者。然后,调用者可以使用该值 (1) 做任何它想做的事。稍后,调用者可能会返回一个值给生成器(通过 generator.next()),该值成为 yield 表达式 (2) 的结果,或者一个似乎由 [=10= 抛出的错误] 表达式 (3).

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

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

what the heck is the difference between the await keyword and the yield keyword?

await关键字只能在async function中使用,而yield关键字只能在生成器function*中使用。这些也明显不同 - 一个 returns 承诺,另一个 returns 生成器。

Does await always turn something into a promise, whereas yield makes no such guarantee?

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

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

嗯,原来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/

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

因此,简而言之,生成器为您提供了更大的灵活性,并且基于生成器的库通常具有更多功能。但是 async/await 是这门语言的核心部分,它是标准化的,不会在你手下改变,你也不需要库来使用它。我有一个 blog post,其中包含有关 async/await 和生成器之间区别的更多详细信息。

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

程序 #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');

tl;博士

使用 async/await 99% 的时间超过发电机。 为什么?

  1. async/await 直接替换了最常见的 promise 链工作流程,允许代码被声明为同步的,极大地简化了它。

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

  3. async/await 实际上是一个建立在生成器之上的抽象,使使用 promise 更容易。

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

yield+gen.next()-as-a-language-feature 可用于描述(或实现)await-async 的底层控制流已经抽离了。


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

之上的实现

对此有更直观的理解:

假设我们在异步函数中有 42 awaitsawait A -> await B -> ...

在内心深处,它相当于拥有 yield A -> tries resolve this as a Promise [1]

-> if resolvable, we yield B, and repeat [1] for B

-> if not resolveable, we throw

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

这就是为什么像 redux-saga 这样的 lib 使用生成器将 promise 通过管道传输到 saga 中间件,以便在一个地方全部解析;从而将 Promises 构造与其评估解耦,从而与 Free Monad.

非常相似

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

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

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