async,await的前世今生

206 阅读3分钟

前言

async,await的简单使用在这里就不再赘述,前端的很多痛点在es6,es7中已经被一两个关键词就可以解决了,但最终很多时候被babel转化为es5的语法。

作为一个日常CV前端,我想多了解一点关于它的知识也许某一天可以少一个通宵呢 ╮( ̄▽ ̄)╭

Generator 的碎碎念

相关方法

Generator.prototype.next() 返回一个由 yield表达式生成的值

Generator.prototype.return() 返回给定的值并结束生成器

Generator.prototype.throw() 向生成器抛出一个错误

基础用法

function* gen() {
try {
  let first = yield 1;
  let second = yield first + 2;
  yield second + 3;
  } catch (e){
    console.log(e);
  }
}

var iterator = gen();
console.log(iterator.next());    // {value: 1, done: false}
console.log(iterator.next(4));   // {value: 6, done: false}
console.log(iterator.next());    // {value: NaN, done: false}
console.log(iterator.next());    // {value: undefined, done: true}
console.log(iterator.return();   // { value: undefined, done: true }
console.log(iterator.return(1);   // { value: 1, done: true }
iterator.throw('出错了');    //出错了

如果以以上的答案都能答对,恭喜你基础知识已经都掌握了。

Object 与... ,for of

一个数据结构只要部署了Symbol.iterator属性就能使用 for...of遍历 与 ...运算符 操作

Object身上没有Symbol.iterator,当直接使用时会报错

let obj = {
            0: 'a',
            1: 'b',
            2: 'c',
        }
         
    for(let p of obj){
            console.log(p);//TypeError: obj is not iterable
      }

所以需要在Object上添加一个生成器方法

console.log([ // ... Array.from
    ...{
      0: 1,
      1: 2,
      2: 3,
      length: 3,
      [Symbol.iterator]:function *(){
          let index = 0;
          while(index !== this.length){
              yield this[index++];
          }
      }
    //   [Symbol.iterator]() {
    //     let len = this.length;
    //     let index = 0;
    //     //迭代器 是有next方法 而且方法执行后 需要返回 value,done
    //     return {
    //       next: () => {
    //         return { value: this[index++], done: index === len + 1 };
    //       }
    //     };
    //   }
    }
  ]);

Regenerator 转换器

Generator 编译成低版本可用大致流程为,编译阶段需要处理相应的抽象语法树(ast),生成符合运行时代码的 es5 语法结构。运行时阶段,添加 runtime 函数辅助编译后语句执行。

regenerator 网站 提供可视化操作,简单 ast 转码前后示例如下:

function *range(max, step) {
  var count = 0;
  step = step || 1;

  for (var i = 0; i < max; i += step) {
    count++;
    yield i;
  }

  return count;
}

var gen = range(20, 3), info;

while (!(info = gen.next()).done) {
  console.log(info.value);
}

console.log("steps taken: " + info.value);

转换后

ar _marked =
/*#__PURE__*/
regeneratorRuntime.mark(range);

function range(max, step) {
  var count, i;
  return regeneratorRuntime.wrap(function range$(_context) {
    while (1) {
      switch (_context.prev = _context.next) {
        case 0:
          count = 0;
          step = step || 1;
          i = 0;

        case 3:
          if (!(i < max)) {
            _context.next = 10;
            break;
          }

          count++;
          _context.next = 7;
          return i;

        case 7:
          i += step;
          _context.next = 3;
          break;

        case 10:
          return _context.abrupt("return", count);

        case 11:
        case "end":
          return _context.stop();
      }
    }
  }, _marked);
}

var gen = range(20, 3),
    info;

while (!(info = gen.next()).done) {
  console.log(info.value);
}

console.log("steps taken: " + info.value);

async,await出现前,优秀的解决异步问题的co库

co 是 TJ大神 于 2013 年推出的一个利用 ES6 的 Generator 函数来解决异步操作的开源项目,百行的代码很适合入门阅读。

关键部分

/**
 * Execute the generator function or a generator
 * and return a promise.
 *
 * @param {Function} fn
 * @return {Promise}
 * @api public
 */

function co(gen) {
  //暂存上下文
  var ctx = this;
  //Array.prototype.slice 除去传来的方法名留下参数
  var args = slice.call(arguments, 1);

  // we wrap everything in a promise to avoid promise chaining,
  // which leads to memory leak errors.
  // see https://github.com/tj/co/issues/180
  //co()返回Promise对象
  return new Promise(function(resolve, reject) {
  	//执行一次Generator获取遍历器
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    if (!gen || typeof gen.next !== 'function') return resolve(gen);
	//执行成功回调函数
    onFulfilled();

    /**
     * @param {Mixed} res
     * @return {Promise}
     * @api private
     */

    function onFulfilled(res) {
      var ret;
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      next(ret);
      return null;
    }

    /**
     * @param {Error} err
     * @return {Promise}
     * @api private
     */

    function onRejected(err) {
      var ret;
      try {
        ret = gen.throw(err);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }

    /**
     * Get the next value in the generator,
     * return a promise.
     *
     * @param {Object} ret
     * @return {Promise}
     * @api private
     */
    //此处为精华所在,连续执行next
    function next(ret) {
      //generator执行完毕
      if (ret.done) return resolve(ret.value);
      //每次都将yield后的值转换为promise
      var value = toPromise.call(ctx, ret.value);
      //使用promise.then连续调用
      //onFulfilled, onRejected会继续调用next本身
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
      //yield后的值无法转换为promise的情况
      return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
        + 'but the following object was passed: "' + String(ret.value) + '"'));
    }
  });
}

async,await

async,await 函数是什么?一句话,它就是 Generator 函数的语法糖。

参考文档

Generator 函数的语法

Generator 函数的异步应用

async 函数