JavaScript深刻淺出異步編程3、async、await

前面兩篇文章已經分別分析了setTimeoutsetIntervalPromise了,可是提到異步編程,還有一個沒有分析,那就是ES8的asyncawait。然而提到asyncawait就不得不說yieldgenerator。下面就一一分析javascript

asyncawait

照例,在開始以前先舉例子。下面先定義兩個異步方法。java

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的鏈式調用來實現。好比:ajax

func1().then(function(result1){
    console.log(result1);
    return func2();
}).then(function(result2){
    console.log(result2);
});
複製代碼

上面的代碼等func1執行完畢後打印結果,而後繼續執行func2編程

而後如今有了asyncawait後就能夠改用下面的代碼來寫了:promise

// 串行執行兩個異步方法
async function funcAysnc(){
    var result1 = await func1();
    console.log(result1);
    var result2 = await func2();
    console.log(result2);
}
funcAysnc();
複製代碼

兩種實現方式,明顯第二種看起來舒服多了。看起來真的像是在串行執行兩個異步的代碼。可是這實際上是假象,asyncawait說白了就是語法糖,咱們看下babel編譯後的代碼,先將yieldgenerator編譯插件關了,轉碼獲得以下代碼:babel

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架構

下面繼續剖析generatoryieldapp

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實現代碼。async

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是一個很奇妙的語言,有很大的潛力,幾乎無所不能。

相關文章
相關標籤/搜索