今天,帶大家來談談ES6中的async函式,我們在理解一個概念的時候,無外乎這是三個方面
- 是什麼
- 為什麼
- 怎麼用
如果感覺文章太長,可以直接拉到下面,看小結?
sync是什麼
ES7提供了async函式
,使得非同步操作變得更加方便。async函式
是什麼?一句話,async函式就是Generator函式
的語法糖。
我們來個案例,取讀檔案
Generator函式
var fs = require(`fs`);
var readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) 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());
};
複製程式碼
寫成async函式,就是下面這樣。
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,僅此而已。
我們會想,為什麼明明有Generator函式,還需要async函式
為什麼需要async函式
async函式對 Generator 函式的改進,體現在以下四點。
(1)內建執行器。Generator函式的執行必須靠執行器,所以才有了co模組,而async函式自帶執行器。也就是說,async函式的執行,與普通函式一模一樣,只要一行。
var result = asyncReadFile();
複製程式碼
上面的程式碼呼叫了asyncReadFile函式,然後它就會自動執行,輸出最後結果。這完全不像Generator函式,需要呼叫next方法,或者用co模組,才能得到真正執行,得到最後結果。
(2)更好的語義。async和await,比起星號和yield,語義更清楚了。async表示函式裡有非同步操作,await表示緊跟在後面的表示式需要等待結果。
(3)更廣的適用性。 co模組約定,yield命令後面只能是Thunk函式或Promise物件,而async函式的await命令後面,可以是Promise物件和原始型別的值(數值、字串和布林值,但這時等同於同步操作)。
(4)返回值是Promise。async函式的返回值是Promise物件,這比Generator函式的返回值是Iterator物件方便多了。你可以用then方法指定下一步的操作。
進一步說,async函式完全可以看作多個非同步操作,包裝成的一個Promise物件,而await命令就是內部then命令的語法糖。
語法
async怎麼使用呢?
一個函式前面加上async,就可以讓這個函式數成為非同步函式,跳出原本的執行順序
console.log(1)
async function asyfun () {
console.log(2)
}
asyfun();
console.log(3)
// 列印結果:1,3,2
複製程式碼
(1)async函式返回一個Promise物件。
async函式內部return語句返回的值,會成為then方法回撥函式的引數。
async function f() {
return `hello world`;
}
f().then(v => console.log(v))
// "hello world"
複製程式碼
上面程式碼中,函式f內部return命令返回的值,會被then方法回撥函式接收到。
async函式內部丟擲錯誤,會導致返回的Promise物件變為reject狀態。丟擲的錯誤物件會被catch方法回撥函式接收到。
async function f() {
throw new Error(`出錯了`);
}
f().then(
v => console.log(v),
e => console.log(e)
)
// Error: 出錯了
複製程式碼
(2)async函式返回的Promise物件,必須等到內部所有await命令的Promise物件執行完,才會發生狀態改變。也就是說,只有async函式內部的非同步操作執行完,才會執行then方法指定的回撥函式。
下面是一個例子。
async function getTitle(url) {
let response = await fetch(url);
let html = await response.text();
return html.match(/<title>([sS]+)</title>/i)[1];
}
getTitle(`https://tc39.github.io/ecma262/`).then(console.log)
// "ECMAScript 2017 Language Specification"
複製程式碼
(3)正常情況下,await命令後面是一個Promise物件。如果不是,會被轉成一個立即resolve的Promise物件。
async function f() {
return await 123;
}
f().then(v => console.log(v))
// 123
複製程式碼
上面程式碼中,await命令的引數是數值123,它被轉成Promise物件,並立即resolve。
await命令後面的Promise物件如果變為reject狀態,則reject的引數會被catch方法的回撥函式接收到。
async function f() {
await Promise.reject(`出錯了`);
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出錯了
複製程式碼
注意,上面程式碼中,await語句前面沒有return,但是reject方法的引數依然傳入了catch方法的回撥函式。這裡如果在await前面加上return,效果是一樣的。
只要一個await語句後面的Promise變為reject,那麼整個async函式都會中斷執行。
async function f() {
await Promise.reject(`出錯了`);
await Promise.resolve(`hello world`); // 不會執行
}
複製程式碼
上面程式碼中,第二個await語句是不會執行的,因為第一個await語句狀態變成了reject。
為了避免這個問題,可以將第一個await放在try…catch結構裡面,這樣第二個await就會執行。
async function f() {
try {
await Promise.reject(`出錯了`);
} catch(e) {
}
return await Promise.resolve(`hello world`);
}
f()
.then(v => console.log(v))
// hello world
複製程式碼
另一種方法是await後面的Promise物件再跟一個catch方面,處理前面可能出現的錯誤。
async function f() {
await Promise.reject(`出錯了`)
.catch(e => console.log(e));
return await Promise.resolve(`hello world`);
}
f()
.then(v => console.log(v))
// 出錯了
// hello world
複製程式碼
如果有多個await命令,可以統一放在try…catch結構中。
async function main() {
try {
var val1 = await firstStep();
var val2 = await secondStep(val1);
var val3 = await thirdStep(val1, val2);
console.log(`Final: `, val3);
}
catch (err) {
console.error(err);
}
}
複製程式碼
(4)如果await後面的非同步操作出錯,那麼等同於async函式返回的Promise物件被reject。
async function f() {
await new Promise(function (resolve, reject) {
throw new Error(`出錯了`);
});
}
f()
.then(v => console.log(v))
.catch(e => console.log(e))
// Error:出錯了
複製程式碼
上面程式碼中,async函式f執行後,await後面的Promise物件會丟擲一個錯誤物件,導致catch方法的回撥函式被呼叫,它的引數就是丟擲的錯誤物件。具體的執行機制,可以參考後文的“async函式的實現”。
防止出錯的方法,也是將其放在try…catch程式碼塊之中。
async function f() {
try {
await new Promise(function (resolve, reject) {
throw new Error(`出錯了`);
});
} catch(e) {
}
return await(`hello world`);
}
複製程式碼
小結
- async比generstor更好,語法上更語義化,內建執行器
- async返回值是Promise,函式內部的return值,會成為then方法回撥函式的引數。
- await只能在async函式內部使用
- 如果await後面的非同步操作出錯,那麼等同於async函式返回的Promise物件被reject
本文首發於微信公眾號:node前端