async/await=Generator + Promise ?

都說async/awaitGenerator+Promise的語法糖,經過本文逐步揭開async/await背後的祕密...react

1、使用Generator把異步邏輯同步化

function asyncOp(x) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            Number.isSafeInteger(x) 
            ? resolve(12)
            : reject(new Error('Invalid integer'));
        }, 3000)
    })
}

function *gen(x) {
    try {
        var y = yield asyncOp(x);    
        return x + y;
    } catch(e) {
        console.error(e)
    }
}

var g = gen(1);
// 獲取異步操做
var asyncAction = g.next();

asyncAction.value
.then(value => {
    // 把異步操做的結果值傳給生成器函數
    var result = g.next(value);
    console.log(result.value);
})

總體思路:git

  1. 經過yield返回異步操做(並暫停生成器函數執行);
  2. 經過next方法把異步操做的結果值傳入生成器函數(並繼續執行生成器函數);
    相對於生成器函數裏代碼來講並不關心yield表達式的值是同步仍是異步。
  3. 若是異步操做失敗了,能夠跟經過throw方法在暫定位置拋異常。
var g = gen('a');
var asyncAction = g.next();

asyncAction.value
.catch(reason => {
    // 經過throw告訴生成器函數異常操做發生了異常
    g.throw(reason);
})

2、實踐:異步加法

function getRadom() {
  return (Math.random() * 100) >>> 0;
}

function getRandomAsync() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(getRadom());
    }, 2000)
  })
}

function* sum() {
  var x = yield getRandomAsync();
  console.log(`x=${x}`)
  var y = yield getRandomAsync();
  console.log(`y=${y}`)
  return x + y;
}

1. 最搓的方式:逐步調用

var gen = sum();
// 獲取異步操做1
gen.next().value
.then(val => {
  // 把異步操做1的結果傳給生成器函數,並獲取異步操做2
  gen.next(val).value
  .then(val => {
    // 把異步操做2的結果傳給生成器函數,並獲最終結果
    var sum = gen.next(val).value;
    console.log(`sum=${sum}`)
  })
})

上面的寫法就像記流水帳,若是有3個數相加還這樣寫豈不是要瘋。github

2. 固定的模式的調用方式:

// 生成器函數執行器
function runner(genFunc) {
  // 建立生成器對象
  var gen = genFunc();
  // 開啓執行
  return doRun();

  function doRun(arg) {
    // 把上一個異步操做結果`arg`傳如生成器函數
    var data = gen.next(arg);
    
    if(data.done) {
      return Promise.resolve(data.value);
    }
    // 若是還沒結束,就等異步操做結束後遞歸調用doRun。
    return Promise.resolve(data.value).then(doRun);
  }
}

runner(sum).then(sum => {
  console.log(sum)
})

注意:
這裏使用Promise.resolve方法data.value轉成Promise,由於Promise.resolve的特殊功能:若是實參value是個Promise對象,則直接返回實參babel

3、實踐:處理異步操做的異常

function runner(genFunc) {
  var gen = genFunc(); 

  return new Promise((resolve, reject) => {
    doRun();
    
    function doRun(arg) {
      try {
        // 捕獲`next`方法拋出的異常
        var data = gen.next(arg);
        if(data.done) {
          return resolve(data.value);
        }

         // 若是還沒結束,就等異步操做結束後遞歸調用doRun。
        return Promise.resolve(data.value)
            .then(doRun)
            .catch(gen.throw) // 經過`throw`方法告訴生成器異常了        
            .catch(reject) // 捕獲`throw`方法拋出的異常(即生成器方法沒有捕獲處理異常)        
      } catch(error) {
        reject(error);
      }   
    }                  
  })   
}

runner(sum).then(sum => {
  console.log(sum)
})
.catch(reason => {
  console.error(reason)
})
  1. 異常不只來自throw方法,next方法也可能會拋出異常,因此在最外層使用try-catch捕獲next方法拋出的異常;
  2. runner方法的返回值也再也不是doRun()了,而改爲了Promise,用於處理next方法拋出的異常和最終的結果值。

捕獲throw方法拋出的異常也能夠採用try-catch方式,這樣就跟捕獲next方法拋出的異常保持一致了:app

function runner(genFunc) {
    var gen = genFunc();
 
    return doRun();

    function doRun(arg) {
        return new Promise((resolve, reject) => {
            step('next');

          function step(methodName, arg) {
            try {
                // 捕獲`next`方法拋出的異常
                var data = gen[methodName](arg);
            } catch(error) {
                reject(error);
                return;
            }

            if(data.done) {
                return resolve(data.value);
            }

            return Promise.resolve(data.value)
                .then(value => {
                    step('next', value);
                })
                .catch(reason => {
                    step('throw', reason);
                })
          }
                    
        })   
    }
}

runner(sum)
.then(sum => {
    console.log(sum)
})
.catch(reason => {
    console.error(reason)
})

4、實踐:分析async/awaitGenerator+Promise寫法

Babel如何把async轉成Generator?dom

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

// 負責把`async`轉成`generator`
function _asyncToGenerator(fn) {
    return function () {
        // 處理傳給生成器的參數
        var self = this,
            args = arguments;
            
        return new Promise(function (resolve, reject){
             // 生成器的函數在Promise參數的回調函數裏執行,而且處理參數
            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);
        });
    };
}

使用固定的模式把async轉成Generator,Babel的實現更嚴謹些:異步

  1. 能夠傳參數給生成器函數;
  2. 先轉Generator,調用時再傳參。

參考這個調整以前的實現:async

function _asyncToGenerator(genFunc) {
    return function() {
        var args = arguments;
        var self = this;

        return new Promise((resolve, reject) => {
              var gen = genFunc.apply(self, args); 
              step('next');

          function step(methodName, arg) {
                try {
                    var data = gen[methodName](arg);
                } catch(error) {
                      reject(error);
                      return;
                }

                if(data.done) {
                    return resolve(data.value);
                }

                return Promise.resolve(data.value)
                    .then(value => {
                        step('next', value);
                    })
                    .catch(reason => {
                        step('throw', reason);
                    })
            }            
        })  
    }
}

_asyncToGenerator(sum)()
.then(sum => {
    console.log(sum)
})
.catch(reason => {
    console.error(reason)
})

總結下async/await轉成Genertor方式:函數

  1. await直接替換成yield;
  2. async函數體代碼轉成生成器代碼(匿名的生成器函數);
  3. async函數名被轉成普通函數內部調用生成器函數的函數。
async function sum() {
    var x = await 1;
      var y = await 2;
      return x + y;
}

// 對應的生成器方式
function sum() {
    return _sum.apply(this, arguments);
}

function _sum() {
  // 注意:函數體內的_sum變量是個局部變量,不影響外部做用域下的_sum的值。
  _sum = _asyncToGenerator(function* () {
    var x = yield 1;
    var y = yield 2;
    return x + y;
  });

  // 建立生成器對象,並開始執行生成器
  return _sum.apply(this, arguments);
}

參考

  1. 深刻Generator——異步
相關文章
相關標籤/搜索