JavaScript深入淺出非同步程式設計三、async、await

搬磚的碼農發表於2019-01-10

前面兩篇文章已經分別分析了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是一個很奇妙的語言,有很大的潛力,幾乎無所不能。

來源:https://juejin.im/post/5c354f6fe51d45524c7cb1bb

相關文章