前面兩篇文章已經分別分析了setTimeout
、setInterval
、Promise
了,但是提到非同步程式設計
,還有一個沒有分析,那就是ES8的async
、await
。然而提到async
、await
就不得不說yield
、generator
。下面就一一分析
async
和await
照例,在開始之前先舉例子。下面先定義兩個非同步方法。
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);
});
}複製程式碼
在實際的開發中,兩個非同步方法有可能需要序列執行
,在沒有async
、await
之前那麼直接使用promise
的then
的鏈式呼叫來實現。比如:
func1().then(function(result1){
console.log(result1);
return func2();
}).then(function(result2){
console.log(result2);
});
複製程式碼
上面的程式碼等func1
執行完畢後列印結果,然後繼續執行func2
。
然後現在有了async
、await
後就可以改用下面的程式碼來寫了:
// 序列執行兩個非同步方法async function funcAysnc(){
var result1 = await func1();
console.log(result1);
var result2 = await func2();
console.log(result2);
}funcAysnc();
複製程式碼
兩種實現方式,明顯第二種看起來舒服多了。看起來真的像是在序列執行兩個非同步的程式碼。但是這其實是假象,async
和await
說白了就是語法糖,我們看下babel
編譯後的程式碼,先將yield
和generator
編譯外掛關了,轉碼得到如下程式碼:
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();
複製程式碼
你會看到,babel
將async
和await
轉碼成generator
和yield
了。而generator
和yield
又屬於ES6
的規範,那麼也就是說,實際上ES6本身就可以支援async
和await
。
下面繼續剖析generator
和yield
。
generator
和yield
現在把babel
的yield
外掛開啟。然後看下編譯程式碼:
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();
複製程式碼
你會發現,babel
對yield
和generator
進一步的進行了轉碼。但是如果你現在直接執行的話你會發現,無法執行。報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);
}
}複製程式碼
從上面的程式碼可以看出,不管是async
、await
還是yield
、generator
,都只是語法糖,經過babel
的轉碼,都會轉成ES5
的程式碼。哪怕generator
稍微有點特殊,但是generator
本身的原理並不複雜,哪怕自己寫一個出來都可以。下一篇,我會單獨寫一篇關於generator
的原理剖析。詳細的介紹yield
和generator
的實現原理。
另外,我們也可以從轉碼後的程式碼看出,async
和await
本身也並沒有直接提供非同步程式設計
的能力。僅僅只是個語法糖而已,都是假象。
最後
這裡花了三篇文章著重介紹了在JavaScript
中進行非同步程式設計
的方法以及背後原理,我們會發現,我們平時開發時候常用的Promise
、async
、await
本身壓根就沒有具備非同步
的功能,都是假象。非要說具備非同步
能力的API,那也就剩下setTimeout
、setInterval
、XMLHttpRequest(ajax)
了。
但是哪怕這些是假象,但是在我們平時的開發過程中確實能給我們的開發體驗帶來質的改變,甚至能夠直接影響到整個專案的架構設計。
同時也覺得javascript
是一個很奇妙的語言,有很大的潛力,幾乎無所不能。