在JavaScript中做異步開發時,我們現在會毫不猶豫的使用`Async/Await`。不管是并發還是串行,`Async/Await`都能處理的很好,而且還保證了代碼的可讀性。但是,`Async/Await`是在ES6之后突然出現的, 這激發了我的好奇心,使得我想更進一步的了解它。本文假設讀者是對Promise和Async有一定了解的,因此對基礎部分不做過多的敘述。然后,我將通過以下三個部分來剖析`Async\Await`:
* Generator函數
* 自動執行器
* 內置執行
`Async`函數的實現原理就是將Generator函數和自動執行器包裝在一個函數里。
### Generator函數
### 基本用法
Generator函數是ES6提供的一種異步編程解決方案,它的調用方式與普通函數一樣,也是在函數后面加上一對圓括號。不同的是,調用Generator函數后,該函數并不執行,返回的也不是函數運行結果,而是一個指向內部狀態的指針對象(遍歷器對象)。接著,必須調用遍歷器對象的next方法,內部指針就從函數頭部或上一次停下來的地方開始執行,直到遇到下一條yield語句(或return語句)為止。
~~~js
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next(); // { value: 'hello', done: false }
hw.next(); // { value: 'world', done: false }
hw.next(); // { value: 'ending', done: true }
hw.next(); // { value: undefined, done: true }
~~~
### 異步操作
Ajax是典型的異步操作,我們試著通過Generator函數部署Ajax,來達到同步表達的效果。
~~~js
function* main() {
var result = yield request("url");
var res = JSON.parse(result);
console.log(res.value)
}
function request(url) {
makeAjaxCall(url, function(response) {
it.next(response);
});
}
var it = main();
it.next();
~~~
這樣似乎能滿足我們的要求,但如果我們增加了請求的數量,那結果就不是我們期望的那樣美好了:我們需要不斷的調用next方法來逐個執行異步請求。如果Generator能夠自動執行就好了。
~~~js
function main() {
var result1 = yield request('url1');
var res1 = JSON.parse(result1);
console.log(res1.value);
var result2 = yield request('url2');
var res2 = JSON.parse(result2);
console.log(res2.value)
}
function request(url) {
makeAjaxCall(url, function(response) {
it.next(response);
});
}
var it = main();
it.next();
it.next();
~~~
### 自動執行器
如果要自動執行,我們首先就會想到用循環來處理。
~~~js
function* gen() [
......
}
var g = gen();
var res = g.next();
while(!res.done) {
console.log(res.value);
res = g.next();
}
~~~
上面的代碼中,`gen`自動執行完所有步驟,但對于異步操作而言,必須保證前一步執行完才能執行后一步。根據經驗,JavaScript中的回調函數和Promise剛好滿足這個要求。
### 基于Thunk(回調函數)的自動執行器
JavaScript中`Thunk`函數是用來將多參函數替換成只接受回調函數作為參數的單參函數。通過`Thunk`函數,我們可以將`next`方法作為回調函數傳給 需要異步操作的方法,當異步操作完成時,調用`next`方法從而執行下一個操作,從而達到異步操作同步執行的效果。
~~~js
var fs = require('fs');
var thunk = function(fn) {
return function() {
var args = Array.prototype.slice.call(arguments);
return function(callback) {
args.push(callback);
return fn.apply(this, args);
}
}
};
var readFileThunk = thunk(fs.readFile);
function run(fn) {
var gen = fn();
function next(err, data) {
var result = gen.next(data);
if(result.done) return;
result.value(next);
}
next();
}
function* g() {
var f1 = yield readFile('fileA');
var f2 = yield readFile('fileB');
// ...
var fn = yield readFile('fileN');
}
run(g);
~~~
### 基于Promise對象的自動執行器
除了thunk之外,Promise也可以實現自動執行器。這其中的原理也是類似的,也是在確保異步操作完成的情況下調用`next`方法,從而執行下一次的異步操作。
~~~js
var fs = require('fs');
var readFile = function(fileName) {
return new Promise(function(resolve, reject) {
fs.readFile(fileName, function(resolve, reject) {
if(error) return reject(error);
resolve(data);
});
});
};
var gen = function* () {
var f1 = yield readFile('/etc/fstab');
var f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
}
function run(gen) {
var g = gen();
function next(data) {
var result = g.next(data);
if(result.done) return result.value;
result.value.then(function(data) {
next(data);
});
}
next();
}
run(gen);
~~~
### 內置執行
前面我們通過Generator函數寫了讀取文件的代碼,接下來我們將它們改成async函數的形式。
~~~js
......
var asyncReadFile = async function() {
var f1 = await readFile('/etc/fstab');
var f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString())
}
~~~
通過比較就會發現,async函數就是將Generator函數的星號(\*)替換成async,將yield替換成await,然后內置了一個功能類似`run`的執行器。我們用代碼模擬下一下這個執行器。
~~~js
async function fn(args) {
//......
}
// 等同于
function fn(args) {
return spawn(function* () {
//......
});
}
function spawn(genF) {
return new Promise(function(resolve, reject) {
var gen = genF();
function step(nextF) {
try {
var next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
Promise.resovle(next.value).then(function(v) {
step(function(){
return gen.next(v);
});
}, function(e){
step(function() {
return gen.throw(e);
});
});
}
step(function() {
return gen.next(undefined);
});
});
}
~~~
### 總結
本文從Generator函數、自動執行器再到內置執行,分拆解析了`Async`的原理,希望能對大家有所幫助。
- 開篇卷
- 一.koa基礎
- 1.koa基礎之開發環境搭建
- 2.koa基礎之路由
- 3.koa基礎之路由另一種寫法
- 4.koa基礎之get 傳值 以及獲取 get 傳值
- 5.koa 基礎之動態路由的傳值
- 6.koa基礎之ejs模板的使用
- 7.koa基礎之From表單提交get與post數據
- 8.koa基礎之koa-bodyparser 中間件獲取表單提交的數據
- 9.koa基礎之koa-static 靜態資源中間件 靜態web服務
- 10.koa基礎之koa-art-template 模板引擎的使用
- 11.koa基礎之cookie 的基本使用
- 12.koa基礎之koa中session的使用
- 13.koa基礎之重定向
- 二.koa進階
- koa對文件操作
- 上傳文件
- 上傳單個文件
- 上傳多個文件
- 下載文件
- 下載單個文件
- 下載多個文件
- 參考文章
- koa模塊化路由
- koa 允許跨域
- koa 應用生成器
- koa對數據庫操作
- koa對mongodb的操作
- koa對redis的操作
- koa對mysql的操作
- koa對sqlite操作
- koa與elasticsearch的操作
- koa與PostgreSQL的操作
- koa與Neo4j的操作
- koa-static
- koa的async與await使用
- koa模板引擎
- art-template
- ejs模板引擎
- koa-jsonp使用
- 分頁 jqPaginator_koa
- Koa2 ueditor
- koa-multer
- koa-session
- koa-cors
- koa全局變量定義
- koa-compress中間件
- 全球公用頭像的使用
- token生成
- koa-passport
- Koa RESTful Api接口
- Koa中集成GraphQl實現 Server API 接口
- koa集成Swagger
- koa 二維碼的實現
- 三.koa實戰
- 一.koa與IM實戰
- koa和websocket實戰
- koa與Socket.io實戰
- koa與WebRTC實戰
- 二.koa與Web實戰
- 三.koa與react實戰
- 四.koa與vue實戰
- 五.微信公眾號開發
- 四.koa微服務
- 微服務框架
- Tars.js
- Seneca.js
- dubbo.ts
- 番外篇
- koa開發環境搭建
- Koa中間件
- koa中間件的執行順序
- 淺談koa中間件的實現原理
- async和await詳解
- Async/Await原理解析
- koa文章參考
- 其他參考
- 網上學習資源
- json-server
- Jenkins打包指南
- 前端工作流規范
- 結束篇