ES7 提出的async
函式,終於讓 JavaScript 對於非同步操作有了終極解決方案。No more callback hell。async
函式是 Generator
函式的語法糖。使用 關鍵字 async
來表示,在函式內部使用 await
來表示非同步。
想較於 Generator,Async
函式的改進在於下面四點:
- 內建執行器。Generator 函式的執行必須依靠執行器,而
Aysnc
函式自帶執行器,呼叫方式跟普通函式的呼叫一樣 - 更好的語義。
async
和await
相較於*
和yield
更加語義化 - 更廣的適用性。
co
模組約定,yield
命令後面只能是 Thunk 函式或 Promise物件。而async
函式的await
命令後面則可以是 Promise 或者 原始型別的值(Number,string,boolean,但這時等同於同步操作) - 返回值是 Promise。
async
函式返回值是 Promise 物件,比 Generator 函式返回的 Iterator 物件方便,可以直接使用then()
方法進行呼叫
Async 與其他非同步操作的對比
先定義一個 Fetch 方法用於獲取 github user 的資訊:
function fetchUser() {
return new Promise((resolve, reject) => {
fetch('https://api.github.com/users/superman66')
.then((data) => {
resolve(data.json());
}, (error) => {
reject(error);
})
});
}
複製程式碼
Promise 方式
/**
* Promise 方式
*/
function getUserByPromise() {
fetchUser()
.then((data) => {
console.log(data);
}, (error) => {
console.log(error);
})
}
getUserByPromise();
複製程式碼
Promise 的方式雖然解決了 callback hell,但是這種方式充滿了 Promise的 then()
方法,如果處理流程複雜的話,整段程式碼將充滿 then
。語義化不明顯,程式碼流程不能很好的表示執行流程。
Generator 方式
/**
* Generator 方式
*/
function* fetchUserByGenerator() {
const user = yield fetchUser();
return user;
}
const g = fetchUserByGenerator();
const result = g.next().value;
result.then((v) => {
console.log(v);
}, (error) => {
console.log(error);
})
複製程式碼
Generator 的方式解決了 Promise 的一些問題,流程更加直觀、語義化。但是 Generator 的問題在於,函式的執行需要依靠執行器,每次都需要通過 g.next()
的方式去執行。
async 方式
/**
* async 方式
*/
async function getUserByAsync(){
let user = await fetchUser();
return user;
}
getUserByAsync()
.then(v => console.log(v));
複製程式碼
async
函式完美的解決了上面兩種方式的問題。流程清晰,直觀、語義明顯。操作非同步流程就如同操作同步流程。同時 async
函式自帶執行器,執行的時候無需手動載入。
語法
async 函式返回一個 Promise 物件
async
函式內部 return 返回的值。會成為 then
方法回撥函式的引數。
async function f() {
return 'hello world'
};
f().then( (v) => console.log(v)) // hello world
複製程式碼
如果 async
函式內部丟擲異常,則會導致返回的 Promise 物件狀態變為 reject
狀態。丟擲的錯誤而會被 catch
方法回撥函式接收到。
async function e(){
throw new Error('error');
}
e().then(v => console.log(v))
.catch( e => console.log(e));
複製程式碼
async 函式返回的 Promise 物件,必須等到內部所有的 await 命令的 Promise 物件執行完,才會發生狀態改變
也就是說,只有當 async
函式內部的非同步操作都執行完,才會執行 then
方法的回撥。
const delay = timeout => new Promise(resolve=> setTimeout(resolve, timeout));
async function f(){
await delay(1000);
await delay(2000);
await delay(3000);
return 'done';
}
f().then(v => console.log(v)); // 等待6s後才輸出 'done'
複製程式碼
正常情況下,await 命令後面跟著的是 Promise ,如果不是的話,也會被轉換成一個 立即 resolve 的 Promise
如下面這個例子:
async function f() {
return await 1
};
f().then( (v) => console.log(v)) // 1
複製程式碼
如果返回的是 reject 的狀態,則會被 catch
方法捕獲。
Async 函式的錯誤處理
async
函式的語法不難,難在錯誤處理上。
先來看下面的例子:
let a;
async function f() {
await Promise.reject('error');
a = await 1; // 這段 await 並沒有執行
}
f().then(v => console.log(a));
複製程式碼
如上面所示,當 async
函式中只要一個 await
出現 reject 狀態,則後面的 await
都不會被執行。
解決辦法:可以新增 try/catch
。
// 正確的寫法
let a;
async function correct() {
try {
await Promise.reject('error')
} catch (error) {
console.log(error);
}
a = await 1;
return a;
}
correct().then(v => console.log(a)); // 1
複製程式碼
如果有多個 await
則可以將其都放在 try/catch
中。
如何在專案中使用
依然是通過 babel
來使用。
只需要設定 presets
為 stage-3
即可。
安裝依賴:
npm install babel-preset-es2015 babel-preset-stage-3 babel-runtime babel-plugin-transform-runtime
複製程式碼
修改.babelrc
:
"presets": ["es2015", "stage-3"],
"plugins": ["transform-runtime"]
複製程式碼
這樣就可以在專案中使用 async
函式了。