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

ES6 系列之 Babel 将 Async 编译成了什么样子 #103

Open
mqyqingfeng opened this issue Oct 31, 2018 · 5 comments
Open

ES6 系列之 Babel 将 Async 编译成了什么样子 #103

mqyqingfeng opened this issue Oct 31, 2018 · 5 comments

Comments

@mqyqingfeng
Copy link
Owner

前言

本文就是简单介绍下 Async 语法编译后的代码。

Async

const fetchData = (data) => new Promise((resolve) => setTimeout(resolve, 1000, data + 1))

const fetchValue = async function () {
    var value1 = await fetchData(1);
    var value2 = await fetchData(value1);
    var value3 = await fetchData(value2);
    console.log(value3)
};

fetchValue();
// 大约 3s 后输出 4

Babel

我们直接在 Babel 官网的 Try it out 粘贴上述代码,然后查看代码编译成什么样子:

"use strict";

function _asyncToGenerator(fn) {
  return function() {
    var gen = fn.apply(this, arguments);
    return new Promise(function(resolve, reject) {
      function step(key, arg) {
        try {
          var info = gen[key](arg);
          var value = info.value;
        } catch (error) {
          reject(error);
          return;
        }
        if (info.done) {
          resolve(value);
        } else {
          return Promise.resolve(value).then(
            function(value) {
              step("next", value);
            },
            function(err) {
              step("throw", err);
            }
          );
        }
      }
      return step("next");
    });
  };
}

var fetchData = function fetchData(data) {
  return new Promise(function(resolve) {
    return setTimeout(resolve, 1000, data + 1);
  });
};

var fetchValue = (function() {
  var _ref = _asyncToGenerator(
    /*#__PURE__*/ regeneratorRuntime.mark(function _callee() {
      var value1, value2, value3;
      return regeneratorRuntime.wrap(
        function _callee$(_context) {
          while (1) {
            switch ((_context.prev = _context.next)) {
              case 0:
                _context.next = 2;
                return fetchData(1);

              case 2:
                value1 = _context.sent;
                _context.next = 5;
                return fetchData(value1);

              case 5:
                value2 = _context.sent;
                _context.next = 8;
                return fetchData(value2);

              case 8:
                value3 = _context.sent;

                console.log(value3);

              case 10:
              case "end":
                return _context.stop();
            }
          }
        },
        _callee,
        this
      );
    })
  );

  return function fetchValue() {
    return _ref.apply(this, arguments);
  };
})();

fetchValue();

_asyncToGenerator

regeneratorRuntime 相关的代码我们在 《ES6 系列之 Babel 将 Generator 编译成了什么样子》 中已经介绍过了,这次我们重点来看看 _asyncToGenerator 函数:

function _asyncToGenerator(fn) {
  return function() {
    var gen = fn.apply(this, arguments);
    return new Promise(function(resolve, reject) {
      function step(key, arg) {
        try {
          var info = gen[key](arg);
          var value = info.value;
        } catch (error) {
          reject(error);
          return;
        }
        if (info.done) {
          resolve(value);
        } else {
          return Promise.resolve(value).then(
            function(value) {
              step("next", value);
            },
            function(err) {
              step("throw", err);
            }
          );
        }
      }
      return step("next");
    });
  };
}

以上这段代码主要是用来实现 generator 的自动执行以及返回 Promise。

当我们执行 fetchValue() 的时候,执行的其实就是 _asyncToGenerator 返回的这个匿名函数,在匿名函数中,我们执行了

var gen = fn.apply(this, arguments);

这一步就相当于执行 Generator 函数,举个例子:

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();

var gen = fn.apply(this, arguments) 就相当于 var hw = helloWorldGenerator();,返回的 gen 是一个具有 next()、throw()、return() 方法的对象。

然后我们返回了一个 Promise 对象,在 Promise 中,我们执行了 step("next"),step 函数中会执行:

try {
  var info = gen[key](arg);
  var value = info.value;
} catch (error) {
  reject(error);
  return;
}

step("next") 就相当于 var info = gen.next(),返回的 info 对象是一个具有 value 和 done 属性的对象:

{value: Promise, done: false}

接下来又会执行:

if (info.done) {
  resolve(value);
} else {
  return Promise.resolve(value).then(
    function(value) {
      step("next", value);
    },
    function(err) {
      step("throw", err);
    }
  );
}

value 此时是一个 Promise,Promise.resolve(value) 依然会返回这个 Promise,我们给这个 Promise 添加了一个 then 函数,用于在 Promise 有结果时执行,有结果时又会执行 step("next", value),从而使得 Generator 继续执行,直到 info.done 为 true,才会 resolve(value)

不完整但可用的代码

(function() {
    var ContinueSentinel = {};

    var mark = function(genFun) {
        var generator = Object.create({
            next: function(arg) {
                return this._invoke("next", arg);
            }
        });
        genFun.prototype = generator;
        return genFun;
    };

    function wrap(innerFn, outerFn, self) {
        var generator = Object.create(outerFn.prototype);

        var context = {
            done: false,
            method: "next",
            next: 0,
            prev: 0,
            sent: undefined,
            abrupt: function(type, arg) {
                var record = {};
                record.type = type;
                record.arg = arg;

                return this.complete(record);
            },
            complete: function(record, afterLoc) {
                if (record.type === "return") {
                    this.rval = this.arg = record.arg;
                    this.method = "return";
                    this.next = "end";
                }

                return ContinueSentinel;
            },
            stop: function() {
                this.done = true;
                return this.rval;
            }
        };

        generator._invoke = makeInvokeMethod(innerFn, context);

        return generator;
    }

    function makeInvokeMethod(innerFn, context) {
        var state = "start";

        return function invoke(method, arg) {
            if (state === "completed") {
                return { value: undefined, done: true };
            }

            context.method = method;
            context.arg = arg;

            while (true) {
                state = "executing";

                if (context.method === "next") {
                    context.sent = context._sent = context.arg;
                }

                var record = {
                    type: "normal",
                    arg: innerFn.call(self, context)
                };

                if (record.type === "normal") {
                    state = context.done ? "completed" : "yield";

                    if (record.arg === ContinueSentinel) {
                        continue;
                    }

                    return {
                        value: record.arg,
                        done: context.done
                    };
                }
            }
        };
    }

    window.regeneratorRuntime = {};

    regeneratorRuntime.wrap = wrap;
    regeneratorRuntime.mark = mark;
})();

"use strict";

function _asyncToGenerator(fn) {
    return function() {
        var gen = fn.apply(this, arguments);
        return new Promise(function(resolve, reject) {
            function step(key, arg) {
                try {
                    var info = gen[key](arg);
                    var value = info.value;
                } catch (error) {
                    reject(error);
                    return;
                }
                if (info.done) {
                    resolve(value);
                } else {
                    return Promise.resolve(value).then(
                        function(value) {
                            step("next", value);
                        },
                        function(err) {
                            step("throw", err);
                        }
                    );
                }
            }
            return step("next");
        });
    };
}

var fetchData = function fetchData(data) {
    return new Promise(function(resolve) {
        return setTimeout(resolve, 1000, data + 1);
    });
};

var fetchValue = (function() {
    var _ref = _asyncToGenerator(
        /*#__PURE__*/
        regeneratorRuntime.mark(function _callee() {
            var value1, value2, value3;
            return regeneratorRuntime.wrap(
                function _callee$(_context) {
                    while (1) {
                        switch ((_context.prev = _context.next)) {
                            case 0:
                                _context.next = 2;
                                return fetchData(1);

                            case 2:
                                value1 = _context.sent;
                                _context.next = 5;
                                return fetchData(value1);

                            case 5:
                                value2 = _context.sent;
                                _context.next = 8;
                                return fetchData(value2);

                            case 8:
                                value3 = _context.sent;

                                console.log(value3);

                            case 10:
                            case "end":
                                return _context.stop();
                        }
                    }
                },
                _callee,
                this
            );
        })
    );

    return function fetchValue() {
        return _ref.apply(this, arguments);
    };
})();

fetchValue();

请原谅我水了一篇文章……

ES6 系列

ES6 系列目录地址:https://github.com/mqyqingfeng/Blog

ES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。

@104gogo
Copy link

104gogo commented Dec 28, 2018

async function foo() {
  console.log(1)
  await console.log(2)
  console.log(4)
}

foo()
console.log(3)

@mqyqingfeng 大佬你好,请问能从源码的角度帮忙分析下,为什么3比4先输出吗?

@104gogo
Copy link

104gogo commented Dec 28, 2018

我好像理解了,希望大佬指点下

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
    try {
        var info = gen[key](arg);
        var value = info.value;
    } catch (error) {
        reject(error);
        return;
    }
    if (info.done) {
        resolve(value);
    } else {
        Promise.resolve(value).then(_next, _throw);
    }
}

每执行完一次 next,如下:

var info = gen[key](arg);

如果 info.done 不为 true,就使用 Promise 等待当前 value 值,如果是个普通值,就进入到 then,继续执行 _next 方法。这样就形成了一个 Promise 的嵌套。

Promise.resolve(value).then(_next, _throw);

如果将 async 函数转换成 promise 的写法,可能是这样的:

const fetchValue = async function () {
  var value1 = await fetchData(1);
  var value2 = await fetchData(value1);
  var value3 = await fetchData(value2);
  return value3;
};

function fetchValue() {
  return new Promise((resolve) => {
    Promise.resolve(fetchData(1)).then((value1) => {
      Promise.resolve(fetchData(value1)).then((value2) => {
        Promise.resolve(fetchData(value2)).then((value3) => {
          resolve(value3);
        });
      });
    });
  });
}

这样将 async 方法换成 promise 的写法,上面3为比4先输出的问题,就很好理解了

@CommanderXL
Copy link

@104gogo

async function foo() {
  console.log(1)
  await console.log(2)
  console.log(4)
}

foo()
console.log(3)

实际上等价于

async function foo() {
  console.log(1)
  await Promise.resolve(console.log(2))
  console.log(4)
}

foo()
console.log(3)

这样写的话看起来就好理解了,先输出1,再输出2,然后有个异步的过程,这样3就会被输出,最终输出4

@hzj1993
Copy link

hzj1993 commented Oct 18, 2020

我认为理解generator和async await从协程和事件循环的角度理解会更好

@Bluestar123
Copy link

async function foo() {
  console.log(1)
  await console.log(2)
  console.log(4)
}

foo()
console.log(3)

@mqyqingfeng 大佬你好,请问能从源码的角度帮忙分析下,为什么3比4先输出吗?

console.log(4) 在await 下一行执行,可以算是在微任务(promise.then中),所以执行完123,在打印4

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

No branches or pull requests

5 participants