generator函数和async/await

generator函数与普通函数的区别:多个*,并且只有在generator函数中才能使用yield,yield相当于generator函数执行的中途暂停点,可以通过.next继续执行下一个,.next方法返回一个对象,{value: any, done: Boolean}

  • value: 暂停点后面接的值,即yield后面的值
  • done:generator函数是否已经结束,是 true,否 false
js 复制代码
function* gen() {
  yield 1;
  yield 2;
  yield 3;
  // return 8;
}
const g = gen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: 3, done: false }
console.log(g.next()); // { value: undefined, done: true } | { value: 8, done: true } 最后一个value值取决于generator函数有没有返回值

一 、generator

1. yield后面接函数

js 复制代码
// 执行到了对应的yield时,会立刻执行此函数,并且函数的返回值会被赋给yield暂停点对象value
function fn(num) {
  return num * 2;
}

function* gen() {
  yield fn(1);
  yield fn(2);
  return 10;
}
const g = gen();
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: 4, done: false }
console.log(g.next()); // { value: 10, done: true }

2. yield后面接Promise

js 复制代码
function fn(num) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(num * 2);
    }, 1000);
  });
}

function* gen() {
  yield fn(1);
  yield fn(2);
  return 10;
}

const g = gen();
// console.log(g.next()); // { value: Promise { <pending> }, done: false }
// console.log(g.next()); // { value: Promise { <pending> }, done: false }
// console.log(g.next()); // { value: 10, done: true }

// 若想要获取promise的结果,可以使用.then
g.next().value.then((val1) => {
  console.log(val1);
  g.next().value.then((val2) => {
    console.log(val2);
  });
});

3. next函数传参

generator函数可以用next方法来传参,并且可以通过yield来接收这个入参,注意以下两点:

  1. 第一次next传参是没有作用的,只有从第二次开始next传参才有用
  2. next传值时,顺序为:先执行右边的yield,然后左边接收的是下一次next的传参
js 复制代码
function* gen() {
  const test2 = yield 1;
  console.log(test2);
  const test3 = yield 2;
  console.log(test3);
  return 10;
}

const g = gen();
console.log(g.next());
// { value: 1, done: false }

console.log(g.next("test2"));
// test2
// { value: 2, done: false }

console.log(g.next("test3"));
// test3
// { value: 10, done: true }

4. Promise & next传参

js 复制代码
function fn(num) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(num * 2);
    }, 1000);
  });
}

function* gen() {
  const test2 = yield fn(1);
  console.log(test2); // 2
  const test3 = yield fn(test2);
  console.log(test3); // 4
  const test4 = yield fn(test3);
  console.log(test4); // 8
  return test4;
}

const g = gen();
g.next().value.then((test2) => {
  console.log(test2); // 2
  g.next(test2).value.then((test3) => {
    console.log(test3); // 4
    g.next(test3).value.then((test4) => {
      console.log(test4); // 8
      g.next(test4);
    });
  });
});

二、async/await

generator函数的Promise & next传参,类似async/await,区别在于:

  1. gen函数的执行返回值不是Promise,asyncFn执行(即asyncFn())返回值是Promise;
  2. gen函数需要执行相应的操作,才能等同async Fn的排队效果
  3. gen函数执行的操作是不完善的,因为并不确定有多少yield,不确定会嵌套几次

针对这种情况,可以通过高阶函数(HOC)封装:

高阶函数: 参数是函数,返回值也可以是函数

js 复制代码
function fn(nums) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(nums * 2);
    }, 1000);
  });
}

function* gen() {
  const num1 = yield fn(1);
  const num2 = yield fn(num1);
  const num3 = yield fn(num2);
  return num3;
}

function generatorToAsync(genFn) {
  return function () {
    return new Promise((resolve, reject) => {
      const g = genFn();
      g.next().value.then((num1) => {
        g.next(num1).value.then((num2) => {
          g.next(num2).value.then((num3) => {
            resolve(g.next(num3).value);
          });
        });
      });
    });
  };
}

const asyncFn = generatorToAsync(gen); // 返回Promise
asyncFn().then((val) => console.log(val)); // 3s后 打印8
js 复制代码
// 以上完成了async/await基础功能,等同于:
async function asyncFn() {
  const num1 = await fn(1);
  const num2 = await fn(num1);
  const num3 = await fn(num2);
  return num3;
}
asyncFn().then((res) => console.log(res));

但是上述的async await数量是固定的,对于不固定数量的await,做出以下改造:

js 复制代码
function fn(nums) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // resolve(nums * 2);
      // reject("err");
    }, 1000);
    throw "err";
  });
}

function* gen() {
  const num1 = yield fn(1);
  const num2 = yield fn(num1);
  const num3 = yield fn(num2);
  const num4 = yield fn(num3);
  const num5 = yield fn(num4);
  return num5;
}

function generatorToAsync(genFn) {
  return function () {
    return new Promise((resolve, reject) => {
      const g = genFn.apply(this, arguments); // genFn可能存在入参
      function goNext(key, nextArg) {
        let yieldValue;
        try {
          yieldValue = g[key](nextArg);
          const { value, done } = yieldValue;
          // value有可能是:常量,Promise,Promise有可能是成功或者失败
          if (value instanceof Promise) {
            if (done) {
              // 如果done为true,说明走完了,进行resolve(value)
              resolve(value);
            } else {
              // 如果done为false,说明没走完,还得继续走
              value.then(
                (res) => goNext("next", res),
                (err) => goNext("throw", err) // yield.throw()方法‌允许在Generator函数内部抛出错误,并且这个错误可以被外部的catch语句捕获
              );
            }
          } else {
            resolve(value);
          }
        } catch (error) {
          // 此处捕获yield.throw()抛出的异常
          reject(error);
        }
      }
      goNext("next");
    });
  };
}

const asyncFn = generatorToAsync(gen); // 返回Promise
asyncFn().then(
  (val) => console.log(val),
  (err) => console.log(err)
); // 3s后 打印8
相关推荐
萧行之2 小时前
可视化技术复盘:从图形语法看 D3、Vega-Lite、ECharts、Observable Plot
前端·学习·数据可视化
尾善爱看海9 小时前
Vue 面试收官篇:SSR、性能优化落地、30 道高频面试题精讲(附标准答案)
前端·javascript·vue.js·面试·vue
驳是10 小时前
一个组件,提升 react-router 开发的幸福感
前端·preact
GreenTea10 小时前
OpenAI Agents API 上手实测:一次调用把整个 agent loop 甩给 OpenAI
前端·后端·算法
星云API技术支持10 小时前
企业微信二次开发:群权限设置、成员管理与群资料维护的接口组合实践
java·前端·企业微信
京东云开发者11 小时前
81.8 秒的视频,我们改了 15 个版本:一次纯 Codex 驱动的 AI-native 视频实践
前端·aigc
Csvn11 小时前
TypeScript 大型项目架构:从单体 tsconfig 到分层可扩展的工程
前端
计算机魔术师13 小时前
Dario Amodei 发文呼吁为前沿 AI 降速并提出三点计划
前端
计算机魔术师13 小时前
OpenAI的AI代理偷偷给RubyGems下毒,我们却毫无察觉
前端
甲维斯13 小时前
0代码,0建模,3句话开发一个3D游戏!
前端·游戏·游戏开发