JavaScript深入浅出异步编程三、async、await

347 阅读4分钟

前面两篇文章已经分别分析了setTimeoutsetIntervalPromise了,但是提到异步编程,还有一个没有分析,那就是ES8的asyncawait。然而提到asyncawait就不得不说yieldgenerator。下面就一一分析

asyncawait

照例,在开始之前先举例子。下面先定义两个异步方法。

function func1() {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve('func1 result');
    }, 1000);
  });
}

function func2() {
  return new Promise(function (resolve) {
    setTimeout(function () {
      resolve('func2 result');
    }, 1000);
  });
}

在实际的开发中,两个异步方法有可能需要串行执行,在没有asyncawait之前那么直接使用promisethen的链式调用来实现。比如:

func1().then(function(result1){
    console.log(result1);
    return func2();
}).then(function(result2){
    console.log(result2);
});

上面的代码等func1执行完毕后打印结果,然后继续执行func2

然后现在有了asyncawait后就可以改用下面的代码来写了:

// 串行执行两个异步方法
async function funcAysnc(){
    var result1 = await func1();
    console.log(result1);
    var result2 = await func2();
    console.log(result2);
}
funcAysnc();

两种实现方式,明显第二种看起来舒服多了。看起来真的像是在串行执行两个异步的代码。但是这其实是假象,asyncawait说白了就是语法糖,我们看下babel编译后的代码,先将yieldgenerator编译插件关了,转码得到如下代码:

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); } }

function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }

function funcAysnc() {
    return _funcAysnc2.apply(this, arguments);
}
function _funcAysnc2() {
    _funcAysnc = _asyncToGenerator(
        function* _callee() {
            var result1 = yield func1();
            console.log(result1);
            var result2 = yield func2();
            console.log(result2);
        }
    );
    return _funcAysnc.apply(this, arguments);
}

funcAysnc();

你会看到,babelasyncawait转码成generatoryield了。而generatoryield又属于ES6的规范,那么也就是说,实际上ES6本身就可以支持asyncawait

下面继续剖析generatoryield

generatoryield

现在把babelyield插件打开。然后看下编译代码:

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); } }

function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }

function funcAysnc() {
  return _funcAysnc.apply(this, arguments);
}

function _funcAysnc() {
  _funcAysnc = _asyncToGenerator(regeneratorRuntime.mark(function _callee() {
    var result1, result2;
    return regeneratorRuntime.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            _context.next = 2;
            return func1();

          case 2:
            result1 = _context.sent;
            console.log(result1);
            _context.next = 6;
            return func2();

          case 6:
            result2 = _context.sent;
            console.log(result2);

          case 8:
          case "end":
            return _context.stop();
        }
      }
    }, _callee, this);
  }));
  return _funcAysnc.apply(this, arguments);
}

funcAysnc();

你会发现,babelyieldgenerator进一步的进行了转码。但是如果你现在直接运行的话你会发现,无法运行。报regeneratorRuntime is not defined这样的错误,也就是说没有找到regeneratorRuntime这个对象,这说明什么?说明generator好像并不能直接通过转码直接运行,如果你百度下的话,人家会告诉你需要为babel添加transform-runtime这个插件重新编译。添加后确实能运行了,但是transform-runtime做了什么呢?我们能否直接写一个regeneratorRuntime呢?答案当然可以,而且实现起来也很简单,下面直接贴出regeneratorRuntime实现代码。

var regeneratorRuntime = new function () {
    function Context() {
        this.prev = 0;
        this.next = 0;
        this.sent = null;
        this.isDone = false;
        this.abrupt = function (op, value) {
            this.isDone = true;
            if (op.toString() === 'return') {
                return value;
            }
            return undefined;
        }

        this.stop = function () {
            this.isDone = true;
            return undefined;
        }
    }
    function RegeneratorYield(func, target) {
        _context = new Context();
        this.next = function (sent) {
            var result = null;
            _context.sent = sent;
            if (_context.isDone) {
                result = undefined;
            } else {
                result = func.call(target, _context);
            }
            _context.sent = null;
            return { value: result, done: _context.isDone };
        }
    }
    this.mark = function (func) {
        return func;
    }
    this.wrap = function (func, mark, target) {
        return new RegeneratorYield(func, target);
    }
}

从上面的代码可以看出,不管是asyncawait还是yieldgenerator,都只是语法糖,经过babel的转码,都会转成ES5的代码。哪怕generator稍微有点特殊,但是generator本身的原理并不复杂,哪怕自己写一个出来都可以。下一篇,我会单独写一篇关于generator的原理剖析。详细的介绍yieldgenerator的实现原理。

另外,我们也可以从转码后的代码看出,asyncawait本身也并没有直接提供异步编程的能力。仅仅只是个语法糖而已,都是假象。

最后

这里花了三篇文章着重介绍了在JavaScript中进行异步编程的方法以及背后原理,我们会发现,我们平时开发时候常用的Promiseasyncawait本身压根就没有具备异步的功能,都是假象。非要说具备异步能力的API,那也就剩下setTimeoutsetIntervalXMLHttpRequest(ajax)了。

但是哪怕这些是假象,但是在我们平时的开发过程中确实能给我们的开发体验带来质的改变,甚至能够直接影响到整个项目的架构设计。

同时也觉得javascript是一个很奇妙的语言,有很大的潜力,几乎无所不能。