简单手写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")
    })
  }
}

引用

目录
相关文章
|
6月前
|
Python
python函数的调用
python函数的调用
|
2天前
|
Python
什么是函数?函数分为几种? 区别是什么?
什么是函数?函数分为几种? 区别是什么?
|
2天前
|
JavaScript 前端开发
什么是函数?函数分为几种?区别是什么?
什么是函数?函数分为几种?区别是什么?
10 0
|
2天前
|
存储 Serverless Python
函数调用的过程
函数调用的过程
21 0
|
11月前
|
Python
19.从入门到精通:Python函数 定义一个函数 函数调用 参数传递
19.从入门到精通:Python函数 定义一个函数 函数调用 参数传递
|
C语言
c 语言 无参无返函数,函数的调用
函数:c语言模块编程的核心语法。 作用:将代码按照功能划分为一段一段的独立的代码,有利于代码的管理,调试,维护。
86 1
|
C语言 C++
字符串处理函数和内存处理函数的模拟实现
上述就是几个常见的字符串处理函数,我们来模拟实现的目的就是为了以后不能使用这种函数的时候能自己写,主要是学习方法。
44 0
字符串处理函数和内存处理函数的模拟实现
|
JavaScript 前端开发
特殊函数:匿名函数的两种应用(①回调函数②自调函数)
特殊函数:匿名函数的两种应用(①回调函数②自调函数)
108 0
特殊函数:匿名函数的两种应用(①回调函数②自调函数)
|
机器学习/深度学习 开发工具 开发者
函数调用函数| 学习笔记
快速学习函数调用函数
85 0
函数调用函数| 学习笔记