基本概念
ES2017 標準引入了 async 函式,使得非同步操作變得更加方便。
async 函式是什麼?一句話,它就是 Generator 函式的語法糖。
前文有一個 Generator 函式,依次讀取兩個檔案。
const fs = require('fs');
const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error);
resolve(data);
});
});
};
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
複製程式碼
上面程式碼的函式gen可以寫成async函式,就是下面這樣。
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
複製程式碼
一比較就會發現,async函式就是將 Generator 函式的星號(*)替換成async,將yield替換成await,僅此而已。
async函式對 Generator 函式的改進,體現在以下四點。
(1)內建執行器。
Generator 函式的執行必須靠執行器,所以才有了co模組,而async函式自帶執行器。也就是說,async函式的執行,與普通函式一模一樣,只要一行。
asyncReadFile();
複製程式碼
上面的程式碼呼叫了asyncReadFile函式,然後它就會自動執行,輸出最後結果。這完全不像 Generator 函式,需要呼叫next方法,或者用co模組,才能真正執行,得到最後結果。
(2)更好的語義。
async和await,比起星號和yield,語義更清楚了。async表示函式裡有非同步操作,await表示緊跟在後面的表示式需要等待結果。
(3)更廣的適用性。
co模組約定,yield命令後面只能是 Thunk 函式或 Promise 物件,而async函式的await命令後面,可以是 Promise 物件和原始型別的值(數值、字串和布林值,但這時會自動轉成立即 resolved 的 Promise 物件)。
(4)返回值是 Promise。
async函式的返回值是 Promise 物件,這比 Generator 函式的返回值是 Iterator 物件方便多了。你可以用then方法指定下一步的操作。
進一步說,async函式完全可以看作多個非同步操作,包裝成的一個 Promise 物件,而await命令就是內部then命令的語法糖。
async 函式的實現原理
async 函式的實現原理,就是將 Generator 函式和自動執行器,包裝在一個函式裡。
async function fn(args) {
// ...
}
// 等同於
function fn(args) {
return spawn(function* () {
// ...
});
}
複製程式碼
所有的async函式都可以寫成上面的第二種形式,其中的spawn函式就是自動執行器。
下面給出spawn函式的實現,基本就是前文自動執行器的翻版。
function spawn(genF) {
return new Promise(function(resolve, reject) {
const gen = genF();
function step(nextF) {
let next;
try {
next = nextF();
} catch(e) {
return reject(e);
}
if(next.done) {
return resolve(next.value);
}
Promise.resolve(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); });
});
}
複製程式碼