JavaScript非同步處理
構建一個應用程式總是會面對非同步呼叫,不論是在 Web 前端介面,還是 Node.js 服務端都是如此,JavaScript 裡面處理非同步呼叫一直是非常噁心的一件事情。以前只能通過回撥函式,後來漸漸又演化出來很多方案,最後 Promise 以簡單、易用、相容性好取勝,但是仍然有非常多的問題。其實 JavaScript 一直想在語言層面徹底解決這個問題,在 ES6 中就已經支援原生的 Promise,還引入了 Generator 函式,終於在 ES7 中決定支援 async 和 await。
基本語法
async/await 究竟是怎麼解決非同步呼叫的寫法呢?簡單來說,就是將非同步操作用同步的寫法來寫。先來看下最基本的語法(ES7 程式碼片段):
const f = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(123);
}, 2000);
});
};
const testAsync = async () => {
const t = await f();
console.log(t);
};
testAsync();
首先定義了一個函式 f
,這個函式返回一個 Promise,並且會延時 2 秒, resolve
並且傳入值
123。 testAsync
async
,然後函式體中配合使用了 await
,最後執行 testAsync
。整個程式會在
2 秒後輸出 123,也就是說 testAsync
中常量 t
取得了 f
中 resolve
的值,並且通過 await
阻塞了後面程式碼的執行,直到 f
這個非同步函式執行完。
對比 Promise
僅僅是一個簡單的呼叫,就已經能夠看出來 async/await 的強大,寫碼時可以非常優雅地處理非同步函式,徹底告別回撥惡夢和無數的 then
方法。我們再來看下與
Promise 的對比,同樣的程式碼,如果完全使用 Promise 會有什麼問題呢?
const f = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(123);
}, 2000);
});
};
const testAsync = () => {
f().then((t) => {
console.log(t);
});
};
testAsync();
從程式碼片段中不難看出 Promise 沒有解決好的事情,比如要有很多的 then
方法,整塊程式碼會充滿 Promise 的方法,而不是業務邏輯本身,而且每一個 then
方法內部是一個獨立的作用域,要是想共享資料,就要將部分資料暴露在最外層,在 then
內部賦值一次。雖然如此,Promise
對於非同步操作的封裝還是非常不錯的,所以 async/await
是基於 Promise 的, await
後面是要接收一個
Promise 例項。
對比 RxJS
RxJS 也是非常有意思的東西,用來處理非同步操作,它更能處理基於流的資料操作。舉個例子,比如在 Angular2 中 http 請求返回的就是一個 RxJS 構造的 Observable Object,我們就可以這樣做:
$http.get(url)
.map(function(value) {
return value + 1;
})
.filter(function(value) {
return value !== null;
})
.forEach(function(value) {
console.log(value);
})
.subscribe(function(value) {
console.log('do something.');
}, function(err) {
console.log(err);
});
如果是 ES6 程式碼可以進一步簡潔:
$http.get(url)
.map(value => value + 1)
.filter(value => value !== null)
.forEach(value => console.log(value))
.subscribe((value) => {
console.log('do something.');
}, (err) => {
console.log(err);
});
可以看出 RxJS 對於這類資料可以做一種類似流式的處理,也是非常優雅,而且 RxJS 強大之處在於你還可以對資料做取消、監聽、節流等等的操作,這裡不一一舉例了,感興趣的話可以去看下 RxJS 的 API。
這裡要說明一下的就是 RxJS 和 async/await 一起用也是可以的,Observable Object 中有 toPromise
方法,可以返回一個
Promise Object,同樣可以結合 await
使用。當然你也可以只使用 async/await 配合 underscore 或者其他庫,也能實現很優雅的效果。總之,RxJS
與 async/await 不衝突。
異常處理
通過使用 async/await,我們就可以配合 try/catch 來捕獲非同步操作過程中的問題,包括 Promise 中 reject 的資料。
const f = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(234);
}, 2000);
});
};
const testAsync = async () => {
try {
const t = await f();
console.log(t);
} catch (err) {
console.log(err);
}
};
testAsync();
程式碼片段中將 f
方法中的 resolve
改為 reject
,在 testAsync
中,通過 catch
可以捕獲到 reject
的資料,輸出
err 的值為 234。 try/catch
使用時也要注意範圍和層級。如果 try
範圍內包含多個 await
,那麼 catch
會返回第一個 reject
的值或錯誤。
const f1 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(111);
}, 2000);
});
};
const f2 = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(222);
}, 3000);
});
};
const testAsync = async () => {
try {
const t1 = await f1();
console.log(t1);
const t2 = await f2();
console.log(t2);
} catch (err) {
console.log(err);
}
};
testAsync();
如程式碼片段所示, testAsync
函式體中 try
有兩個 await
函式,而且都分別 reject
,那麼 catch
中僅會觸發 f1
的 reject
,輸出的
err 值是 111。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------