Javascript中async和await
//函式宣告 async function foo() {} //函式表示式 const foo = async function () {}; //物件的方法 let obj = { async foo() {} }; obj.foo().then(...) //Class 的方法 class Storage { constructor() { this.cachePromise = caches.open('avatars'); } async getAvatar(name) { const cache = await this.cachePromise; return cache.match(`/avatars/${name}.jpg`); } } const storage = new Storage(); storage.getAvatar('jake').then(…); //箭頭函式 const foo = async () => {};
2:async函式的返回值總是一個Promise
無論async函式有無await操作,其總是返回一個Promise。
-
沒有顯式return,相當於return Promise.resolve(undefined);
-
return非Promise的資料data,相當於return Promise.resolve(data);
-
return Promise, 會得到Promise物件本身
async總是返回Promise,因此,其後面可以直接呼叫then方法,函式內部return返回的值,會成為then回撥函式的引數,函式內部丟擲的錯誤,會被then的第二個函式或catch方法捕獲到
//正常返回值
async function f(){
retrun 'hello world';
}
f().then(v => console.log(v));//hello world
//丟擲錯誤
async function f(){
throw new Error('出錯了');
}
f().then(
v => console.log(v),
e => console.log(e) //Error: 出錯了
)
3. await操作符的值
[rv] = await expression(expression可以是任何值,通常是一個promise)
expression是Promise,rv等於Promise兌現的值,若Promise被拒絕,則丟擲異常,由catch捕獲
expression是非Promise,會被轉換為立即resolve的Promise,rv等於expression
await操作只能用在async函式中,否則會報錯。
4:async就是generator和promise的語法糖
//generator寫法
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。
5:async對generator的改進
- 內建執行器
Generator必須依靠執行器呼叫next方法來自動執行,例如co模組。而async函式自帶執行器,可以自動執行。
- 更好的語義
async和await分別表示非同步和等待,語義更加明確
- 適用性更強
Generator模組後面只能是Thunk函式或Promise物件,而await後面可以是Promise或基本資料型別(如:數字,字串,布林等)
- 返回Promise,可以繼續操作
async函式總是返回一個Promise物件,可以對其進行then呼叫,繼續操作後面的資料,因此,
async函式完全可以看作是多個Promise合成一個Promise物件,而await命令就是內部的then呼叫。
6:async內部的並行呼叫
async配合await都是序列呼叫,但是若有並行呼叫,則應按照以下方式來寫:
1. 變數分別接收Promise
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise();
let bar = await barPromise();
2. 使用Promise.all
let [foo,bar] = await Promise.all([getFoo(),getBar()]);
Promise.all這種寫法有缺陷,一個呼叫報錯,會終止,這個不太符合並行呼叫的初衷。
3. 使用多個async函式
實際上,一個async函式內部包含的呼叫應該是強相關的,沒有依賴關係的函式呼叫不應該放在一個async函式中,分開來邏輯更清晰。
4. 並行執行的一些寫法
1. 不能再內部非async function中使用await
async function dbFuc(db) {
let docs = [{}, {}, {}];
// 報錯,forEach的function是非async,不能使用await
docs.forEach(function (doc) {
await db.post(doc);
});
}
//這裡不需要 async
function dbFuc(db) {
let docs = [{}, {}, {}];
// 可能得到錯誤結果,這樣呼叫也不能得到正確的結果
docs.forEach(async function (doc) {
await db.post(doc);
});
}
2. 迴圈呼叫await可以使用for迴圈或for of迴圈
//for of
async function dbFuc(db) {
let docs = [{}, {}, {}];
for (let doc of docs) {
await db.post(doc);
}
}
//map + Promise.all
async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));
let results = await Promise.all(promises);
console.log(results);
}
//map + for of
async function dbFuc(db) {
let docs = [{}, {}, {}];
let promises = docs.map((doc) => db.post(doc));
let results = [];
for (let promise of promises) {
results.push(await promise);
}
console.log(results);
}
//for迴圈中去請求網頁,若await操作成功,會break退出;若失敗,會catch捕獲,進入下一輪迴圈
const superagent = require('superagent');
const NUM_RETRIES = 3;
async function test() {
let i;
for (i = 0; i < NUM_RETRIES; ++i) {
try {
await superagent.get('http://google.com/this-throws-an-error');
break;
} catch(err) {}
}
console.log(i); // 3
}
test();
7:async的錯誤處理
使用try...catch進行包裹,例如:
async function myFunction() {
try {
await somethingThatReturnsAPromise();
} catch (err) {
console.log(err);
}
}
如果僅僅對一部分錯誤進行處理或者忽略,可以區域性的進行包裹,或者對單獨的promise進行catch,例如:
async function myFunction() {
await somethingThatReturnsAPromise().catch((err)=> {
console.log(err);
})
}
async function myFunction() {
try{
await somethingThatReturnsAPromise();
}
catch(e){}
await somethingElse();
}
Promise的錯誤處理,推薦用async + await來寫:
// 存值
createData(title, successBack, errorBack) {
// 使用key儲存資料
storage.save({
key: title,
data: 'true',
}).then(successBack(), errorBack());
}
改寫為
//存值
async createData1(title, successBack, errorBack) {
try {
// 使用key儲存資料
await storage.save({
key: title,
data: 'true',
});
successBack()
} catch (e) {
errorBack()
}
}
形式上更加清晰一些。
8. async函式的實現原理
async函式就是將執行器和Generator做為一個整體返回。
async function fn(){}
//等同於
function fn(){
return spawn(function* (){
})
}
spawn的實現
function spawn(genF) {
/****
* 返回的是一個promise
*/
return new Promise(function(resolve, reject) {
var gen=genF(); //執行Generator這個方法;
/***
* 執行下一步的方法
* @param fn 一個呼叫Generator方法的next方法
*/
function step(fn) {
//如果有錯誤,則直接返回,不執行下面的await
try {
var next=fn();
}catch (e){
return reject(e)
}
//如果下面沒有yield語句,即Generator的done是true
if(next.done){
return resolve(next.value);
}
Promise.resolve(next.value).then((val)=>{
step(function(){ return gen.next(val) } )
}).catch((e)=>{
step(function(){ return gen.throw(e) } )
})
}
step(function () {
return gen.next();
})
});
}