Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

async/await在打包中的实现 #14

Open
WebRookieSyb opened this issue May 27, 2020 · 3 comments
Open

async/await在打包中的实现 #14

WebRookieSyb opened this issue May 27, 2020 · 3 comments

Comments

@WebRookieSyb
Copy link
Collaborator

WebRookieSyb commented May 27, 2020

起源

为了在老工程中使用vue和react等,目前老项目使用rollup-to-nej这个工具打包。在观察打包生成后代代码的时候,发现有挺多的一些辅助函数,这些辅助函数会被重复的加入到我们打包生成的文件中,比如当我们使用async/await的时候。解决方法就是引入babel-runtimebabel-helper并修改babel配置来移除这些辅助函数。

当我比较修改之后的打包文件的时候,发现用到async/await的时候少了这么两个辅助函数。
那么到底打包的时候是如何实现async/await的呢?

实现

async/await实目前用的最多异步解决方案,它其实是Generator的一种语法糖,可以理解为一个自执行的generator。通常在babel或者ts打包为,一般会实现已下两步。

  • 实现一个自执行的Generator
  • 实现一个Generator(打包为es5)

实现一个自执行的Generator

通常来说我们通过已下两种方式实现:

  • 通过不断进行回调函数的执行,直到全部过程执行完毕,基于这种思路的是thunkify模块;
  • 使用Javascript原生支持的Promise对象,将异步过程扁平化处理,基于这种思路的是co模块。

babel和ts在打包的时候都是使用co模块的思路通过Promise来实现,将已下测试代码在ts中转为es5

async function doTest(asyncFn, name) {
  console.log(0)
  const result = await asyncFn(name);
  console.log(result)
  console.log(1)
  await asyncFn()
}
"use strict";
// async/await的实现
// 参数generator就是一个generator函数
// 参数P默认传void 0,取Promise
var __awaiter =
  (this && this.__awaiter) ||
  function (thisArg, _arguments, P, generator) {
    // adopt返回一个promise
    function adopt(value) {
      return value instanceof P
        ? value
        : new P(function (resolve) {
            resolve(value);
          });
    }

    return new (P || (P = Promise))(function (resolve, reject) {
      // promise的fulfilled函数,会去调用generator.next
      function fulfilled(value) {
        try {
          step(generator.next(value));
        } catch (e) {
          reject(e);
        }
      }
      // promise的rejected函数,会去调用generator.throw
      function rejected(value) {
        try {
          step(generator["throw"](value));
        } catch (e) {
          reject(e);
        }
      }
      // step判断generater函数有没有执行完
      function step(result) {
        // generater函数执行完的result值的done为true,执行完则resolve()
        // 否则生成一个Promise继续执行next
        result.done
          ? resolve(result.value)
          : adopt(result.value).then(fulfilled, rejected);
      }
      // 开始执行generator
      step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
  };
  // 原来的async和await会转换成一个generator实现
  function doTest(asyncFn, name) {
    return __awaiter(this, void 0, void 0, function* () {
        console.log(0);
        const result = yield asyncFn(name);
        console.log(result);
        console.log(1);
        yield asyncFn();
    });
}

看完ts的实现,在去看babel的实现(如图1中所示),会发现他其实就是多拆分成了一个函数而已,实现方法都是相同的。

实现一个Generator生成器

因为因为Generator是es6的新语法,所以在babel和ts中打包为es5的代码时需要去实现一个Generator生成器。生成器的强大之处在于能方便地对生成器函数内部的逻辑进行控制。在生成器函数内部,通过yieldyield*,将当前生成器函数的控制权移交给外部,外部通过调用生成器的nextthrowreturn方法将控制权返还给生成器函数,并且还能够向其传递数据。

生成器并非由引擎从底层提供额外的支持,我们可以将生成器视为一个语法糖,用一个辅助工具将生成器函数转换为普通的Javascript代码,在经过转换的代码中,有两个关键点,一是要保存函数的上下文信息,二是实现一个完善的迭代方法,使得多个yield表达式按序执行,从而实现生成器的特性。

在babel中实现一个Generator

  function _doTest() {
    _doTest = _asyncToGenerator(
    /*#__PURE__*/
    regeneratorRuntime.mark(function _callee(asyncFn, name) {
      var result;
      return regeneratorRuntime.wrap(function _callee$(_context) {
        while (1) {
          switch (_context.prev = _context.next) {
            case 0:
              console.log(0);
              _context.next = 3;
              return asyncFn(name);

            case 3:
              result = _context.sent;
              console.log(result);
              console.log(1);
              _context.next = 8;
              return asyncFn();

            case 8:
            case "end":
              return _context.stop();
          }
        }
      }, _callee);
    }));
    return _doTest.apply(this, arguments);
  }
  • yield被转换为switch case,_context保存着当前函数的上下文状态

    我们可以把switch case看做看做是一个状态机,根据_context的状态来执行不同的代码。

  • 函数被regeneratorRuntime.mark包装,返回一个被regeneratorRuntime.wrap包装的迭代器对象。
    简单来说,我们可以理解为实现了一个带有next\return\throw等属性的一个迭代器对象,babel中使用regenerator生成。如果了解regenerator的实现,可以看一下ES6 系列之 Babel 将 Generator 编译成了什么样子
    迭代器对象

我么也可以简单的看下ts中generator的实现。

var __generator = (this && this.__generator) || function (thisArg, body) {
    // _保存着generator的上下文状态 
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    // 返回一个遍历器对象
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    // 调用step
    function verb(n) { return function (v) { return step([n, v]); }; }
    //根据op调用,首次执行时op为[0, undefined],后续为body的返回值
    function step(op) {

        if (f) throw new TypeError("Generator is already executing.");
        // 
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                // 返回未完成
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            //每次next调用后都去执行body,_为上下文状态
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        // 执行完成  此时op[0]为2,0b10 & 0b101 为0。
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
function doTest(asyncFn, name) {
    return __awaiter(this, void 0, void 0, function () {
        var result;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0:
                    console.log(0);
                    return [4 /*yield*/, asyncFn(name)];
                case 1:
                    result = _a.sent();
                    console.log(result);
                    console.log(1);
                    return [4 /*yield*/, asyncFn()];
                case 2:
                    _a.sent();
                    return [2 /*return*/];
            }
        });
    });
}

它的实现也遵循了使用switch case来实现yield,_来保存上下文状态。在__generator函数中实现了一个迭代器对象,有着next,throw,return等方法。

@WebRookieSyb
Copy link
Collaborator Author

babel-runtime主要是引用了 core-js 和 regenerator,babel-helper主要引用babel的一些辅助函数

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants
@WebRookieSyb and others