异步/等待和 ES6 与生成器的产量之间的区别

Difference between async/await and ES6 yield with generators

本文关键字:区别 之间 等待 ES6 异步      更新时间:2023-09-26

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

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/await 实现 async 关键字的方式。所以问题是,如果是这样的话,那么await关键字和yield关键字之间的区别到底是什么?await总是把某事变成承诺,而yield却没有这样的保证吗?这是我最好的猜测!

您还可以在本文中看到 async/await 与生成器yield相似之处,他描述了"生成"函数 ES7 异步函数。

好吧,事实证明,async/await和生成器之间存在非常密切的关系。我相信async/await将永远建立在发电机上。如果你看看 Babel 的转译方式 async/await

巴别塔是这样说的:

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/

yield可以被认为是await的构建块。 yield 获取给定的值并将其传递给调用方。然后,调用方可以对该值 (1( 执行任何它想要的操作。稍后,调用方可能会将一个值返回给生成器(通过 generator.next() (,该值将成为yield表达式 (2( 的结果,或者看起来像是由yield表达式 (3( 抛出的错误。

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

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

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

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

await总是把某事变成承诺,而yield却没有这样的保证吗?

是的,await会根据等待的值调用Promise.resolve

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

tl;dr

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

  1. async/await直接取代了承诺链最常见的工作流程,允许代码像同步一样声明,大大简化了它。

  2. 生成器抽象了一个用例,在该用例中,您将调用一系列相互依赖的异步操作,最终将处于"完成"状态。最简单的示例是分页浏览最终返回最后一个集合的结果,但您只会根据需要调用页面,而不是立即连续调用。

  3. 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');

在许多方面,生成器是 async/await 的超集。现在 async/await 具有比 co 更干净的堆栈跟踪,co是最受欢迎的基于 async/await 的生成器库。您可以使用生成器实现自己的 async/await 风格并添加新功能,例如内置支持对非承诺的yield或在 RxJS 可观察量上构建它。

因此,简而言之,生成器为您提供了更大的灵活性,而基于生成器的库通常具有更多功能。但是 async/await 是该语言的核心部分,它是标准化的,不会在你的领导下改变,你不需要库来使用它。我有一篇博客文章,详细介绍了异步/等待和生成器之间的区别。

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

<小时 />

正如其他答案所暗示的那样,await即语言功能是(或可以认为(基于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 利用 generator 将 promise 传送到saga 中间件,以便在一个地方解决;从而将 Promise 结构与其评估分离,从而与 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);
})();