简单手写async函数,让生成器函数自动执行

简介: 简单手写async函数,让生成器函数自动执行

简单手写async函数,让生成器函数自动执行


async函数实现了异步代码以近乎同步的方式写,可读性强,使用的时候真是非常方便。

其本质是生成器函数的语法糖,本文尝试手写一个函数来替换async函数,去掉糖衣看看。

async函数的 demo

先来一个async函数的 demo:

const getData = () =>
  new Promise((resolve) => setTimeout(() => resolve('data'), 1000));
async function test() {
  const data = await getData();
  console.log('data: ', data);
  const data2 = await getData();
  console.log('data2: ', data2);
  return 'success';
}
// 这样的一个函数 应该再1秒后打印data 再过一秒打印data2 最后打印success
test().then((res) => console.log(res));

换成生成器函数的 demo

每个async函数,其实是在类似执行一个生成器函数,而转化成生成器函数也很简单:

  • 去掉async
  • 加上*
  • await换成yield

示例转化下:

const getData = () =>
  new Promise((resolve) => setTimeout(() => resolve('data'), 1000));
function* testGen() {
  const data = yield getData();
  console.log('data: ', data);
  const data2 = yield getData();
  console.log('data2: ', data2);
  return 'success';
}

生成器函数的基础知识

生成器函数虽然是个函数,但是其返回是一个迭代器

运行完整个生成器函数的话,next的调用次数 = yield的出现次数 + 1

先看个简单的 demo:

function* gen() {
  const data = yield '第 1 个yield';
  console.log('data: ', data);
  const data2 = yield '第 2 个yield';
  console.log('data2: ', data2);
  return 'success';
}
const it = gen();
var { value, done } = it.next('第 1 个next');
var { value, done } = it.next('第 2 个next');
var { value, done } = it.next('第 3 个next');

理解nextyield语句尤为关键

  • next让函数内部代码开始执行,next 本身是个函数,
  • 返回值是一个对象,第n个next函数的返回值(value属性) 是 第nyield后面的内容 或 生成器函数的返回值,done是标志函数是否执行完。
  • 但是第nyield,却是替换成 第n+1next的参数
  • next执行的时候,遇到yield就暂停

将生成器函数以伪代码的方式,表达下:

伪代码里将yieldnext全部换掉,并注上暂停的点,这样我觉得就理解的七七八八了

function* gen() {
  // const data = yield '第 1 个yield';
  const data = '第 2 个next'; // 第 2 个 next的参数,这也是第一个暂停处,注意并没赋值
  console.log('data: ', data);
  // const data2 = yield '第 2 个yield';
  const data2 = '第 3 个next'; // 第 3 个 next的参数,这也是第二个暂停处,注意并没赋值
  console.log('data2: ', data2);
  return '生成器函数的返回值'; // 生成器函数运行完毕
}
// it是迭代器
const it = gen();
// 第一个next函数的返回值是 第一个yield后面的内容
// var { value, done } = it.next('第 1 个next');
var value = '第 1 个yield',
  done = false; // 第一个next函数的返回值是 第一个yield后面的内容
// var { value, done } = it.next('第 2 个next');
var value = '第 2 个yield',
  done = false; // 第二个next函数的返回值是 第二个yield后面的内容
// var { value, done } = it.next('第 3 个next');
var value = '生成器函数的返回值',
  done = true; // 注意,这里done为true,所以value就是是生成器函数的返回值

所以可见,遇到yield赋值语句的时候,一定提醒自己,跟yield后面的内容没有一毛钱关系!

遇到next的赋值语句的时候,valuenext的参数没有一毛钱关系!

让生成器手动执行

再回到主体的生成器函数的例子:

const getData = () =>
  new Promise((resolve) => setTimeout(() => resolve('data'), 1000));
function* testGen() {
  const data = yield getData(); // data = 第2个next的参数
  console.log('data: ', data);
  const data2 = yield getData(); // data = 第3个next的参数
  console.log('data2: ', data2);
  return 'success';
}

想让data是数据的话,跟yield后面的并没关系,而是跟next的传入有关。

const it = testGen();
// value是 第1个yield的后面的getData() 其实就是promise实例,
let res = it.next();
let promise = res.value;
let done = res.done;
promise.then((data) => {
  // promise是 第2个yield的后面的getData() 其实就是promise实例。注意这里的next参数才是上面data的赋值
  res = it.next(data);
  promise = res.value;
  done = res.done;
  promise.then((data) => {
    // done为true,promise是 生成器函数的返回值。注意这里的next参数才是上面data2的赋值
    res = it.next(data);
    promise = res.value;
    done = res.done;
  });
});

让生成器函数自动执行

将上面的过程封装成一个函数,让其自动执行生成器函数,可以将重复的地方拿出来:

function co(gen, ...args) {
  return (...args) => {
    const it = gen(...args);
    // 首次先运行
    let res = it.next();
    let promise = res.value;
    let done = res.done;
    // 重复的部分封装下:
    const fn = () => {
      if (done) {
        return Promise.resolve(promise);
      }
      promise.then((data) => {
        // done为true,promise是 生成器函数的返回值。注意这里的next参数才是上面data2的赋值
        res = it.next(data);
        promise = res.value;
        done = res.done;
        // 继续往前走
        fn();
      });
    };
    fn();
  };
}
co(testGen)();

更好的版本

考虑到异常情况,其实更复杂一点。

这边直接搬运手写 async await 的最简实现(20 行)

function asyncToGenerator(generatorFunc) {
  return function () {
    const gen = generatorFunc.apply(this, arguments);
    return new Promise((resolve, reject) => {
      function step(key, arg) {
        let generatorResult;
        try {
          generatorResult = gen[key](arg);
        } catch (error) {
          return reject(error);
        }
        const { value, done } = generatorResult;
        if (done) {
          return resolve(value);
        } else {
          return Promise.resolve(value).then(
            (val) => step('next', val),
            (err) => step('throw', err)
          );
        }
      }
      step('next');
    });
  };
}

思路:

function asyncToGenerator(generatorFunc) {
  // 返回的是一个新的函数
  return function() {
    // 先调用generator函数 生成迭代器
    // 对应 var gen = testG()
    const gen = generatorFunc.apply(this, arguments)
    // 返回一个promise 因为外部是用.then的方式 或者await的方式去使用这个函数的返回值的
    // var test = asyncToGenerator(testG)
    // test().then(res => console.log(res))
    return new Promise((resolve, reject) => {
      // 内部定义一个step函数 用来一步一步的跨过yield的阻碍
      // key有next和throw两种取值,分别对应了gen的next和throw方法
      // arg参数则是用来把promise resolve出来的值交给下一个yield
      function step(key, arg) {
        let generatorResult
        // 这个方法需要包裹在try catch中
        // 如果报错了 就把promise给reject掉 外部通过.catch可以获取到错误
        try {
          generatorResult = gen[key](arg)
        } catch (error) {
          return reject(error)
        }
        // gen.next() 得到的结果是一个 { value, done } 的结构
        const { value, done } = generatorResult
        if (done) {
          // 如果已经完成了 就直接resolve这个promise
          // 这个done是在最后一次调用next后才会为true
          // 以本文的例子来说 此时的结果是 { done: true, value: 'success' }
          // 这个value也就是generator函数最后的返回值
          return resolve(value)
        } else {
          // 除了最后结束的时候外,每次调用gen.next()
          // 其实是返回 { value: Promise, done: false } 的结构,
          // 这里要注意的是Promise.resolve可以接受一个promise为参数
          // 并且这个promise参数被resolve的时候,这个then才会被调用
          return Promise.resolve(
            // 这个value对应的是yield后面的promise
            value
          ).then(
            // value这个promise被resove的时候,就会执行next
            // 并且只要done不是true的时候 就会递归的往下解开promise
            // 对应gen.next().value.then(value => {
            //    gen.next(value).value.then(value2 => {
            //       gen.next() 
            //
            //      // 此时done为true了 整个promise被resolve了 
            //      // 最外部的test().then(res => console.log(res))的then就开始执行了
            //    })
            // })
            function onResolve(val) {
              step("next", val)
            },
            // 如果promise被reject了 就再次进入step函数
            // 不同的是,这次的try catch中调用的是gen.throw(err)
            // 那么自然就被catch到 然后把promise给reject掉啦
            function onReject(err) {
              step("throw", err)
            },
          )
        }
      }
      step("next")
    })
  }
}

引用

目录
相关文章
|
12月前
|
Python
python函数的调用
python函数的调用
57 0
Python函数:函数的定义和调用
本文详细介绍了Python函数的定义和调用方法,包括基本函数定义、参数传递、返回值、文档字符串、作用域、嵌套函数和闭包。通过一个综合详细的学生成绩管理系统的例子,我们展示了如何在实际编程中应用这些函数概念。希望本文对您理解和应用Python函数有所帮助。
|
6月前
|
存储 Serverless Python
函数调用的过程
函数调用的过程
68 0
手写函数柯里化原理
手写函数柯里化原理记录
59 1
|
C语言
c 语言 无参无返函数,函数的调用
函数:c语言模块编程的核心语法。 作用:将代码按照功能划分为一段一段的独立的代码,有利于代码的管理,调试,维护。
124 1
|
JavaScript 前端开发
特殊函数:匿名函数的两种应用(①回调函数②自调函数)
特殊函数:匿名函数的两种应用(①回调函数②自调函数)
133 0
特殊函数:匿名函数的两种应用(①回调函数②自调函数)
|
JavaScript 前端开发
|
JavaScript 前端开发
|
JavaScript 前端开发