JavaScript體驗異步更好的解決辦法
一、異步解決方案的進化史
JavaScript的異步操作一直是個麻煩事,所以不斷有人提出它的各種解決方案。可以追溯到最早的回調函數(ajax老朋友),到Promise(不算新的朋友),再到ES6的Generator(強勁的朋友)。
幾年前我們可能用過一個比較著名的Async.js,但是它沒有擺脫回調函數,并且錯誤處理也是按照“回調函數的第一個參數用來傳遞錯誤”這樣一個約定。而眾所周知的回調地獄仍然是一個比較突出的問題,直到Generator改變了這種異步風格。
但是ES7的async await的出現(xiàn)(碉堡的新朋友),我們可以輕松寫出同步風格的代碼同時又擁有異步機制,可以說是目前最簡單,最優(yōu)雅,最佳的解決方案了。
二、async await語法
async await語法比較簡單,可以認為是Generator的語法糖,比起星號和yield更具有語義化。下面一個簡單的例子表示1秒之后輸出hello world:
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value)
}
asyncPrint('hello world', 1000);
await只能用在async函數中,如果用在普通函數就會報錯
await后面跟的是一個Promise對象(當然其它值也可以,但是會包裝成一個立即resolve的Promise,也就沒有意義了)
await會等待Promise的結果返回再繼續(xù)執(zhí)行
await等待的雖然是Promise對象,但是不必寫.then(),直接可以得到返回值,將上面的代碼微調,發(fā)現(xiàn)返回值result也是可以輸出hello world:
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(_ => {resolve('hello world')}, ms);
});
}
async function asyncPrint(ms) {
let result = await timeout(ms);
console.log(result)
}
asyncPrint(1000);
三、async await錯誤處理
前面說了await等待的雖然是Promise對象,但是不必寫.then(),所以其實也不用寫.catch()了,直接用try catch就能捕捉錯誤,這樣可以避免錯誤處理代碼非常冗余和笨重,還是將上面的例子微調:
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(_ => {reject('error')}, ms);//reject模擬出錯,返回error
});
}
async function asyncPrint(ms) {
try {
console.log('start');
await timeout(ms);//這里返回了錯誤
console.log('end');//所以這句代碼不會被執(zhí)行了
} catch(err) {
console.log(err); //這里捕捉到錯誤error
}
}
asyncPrint(1000);
如果有多個await,可以一起放在try catch中:
async function main() {
try {
const async1 = await firstAsync();
const async2 = await secondAsync();
const async3 = await thirdAsync();
}
catch (err) {
console.error(err);
}
}
四、async await注意點
1). 前面已經說過,await命令后面的Promise對象,運行結果很可能是reject或邏輯報錯,所以最好把await放在try catch代碼塊中。
2). 多個await命令的異步操作,如果不存在依賴關系,讓它們同時觸發(fā)。
const async1 = await firstAsync(); const async2 = await secondAsync();
上面代碼中,async1和async2如果是兩個獨立的異步操作,這樣寫會比較耗時,因為只有firstAsync完成以后,才會執(zhí)行secondAsync,完全可以用Promise.all優(yōu)雅地處理:
let [async1, async2] = await Promise.all([firstAsync(), secondAsync()]);
3). await只能用在async函數之中,如果用在普通函數就會報錯:
async function main() {
let docs = [{}, {}, {}];
//報錯 await is only valid in async function
docs.forEach(function (doc) {
await post(doc);
console.log('main');
});
}
function post(){
return new Promise((resolve) => {
setTimeout(resolve, 1000);
});
}
在forEach內部方法加上async就可以了:
async function main() {
let docs = [{}, {}, {}];
docs.forEach(async function (doc) {
await post(doc);
console.log('main');
});
}
function post(){
return new Promise((resolve) => {
setTimeout(resolve, 1000);
});
}
但是你會發(fā)現(xiàn)3個main是同時輸出的,這就說明post是并發(fā)執(zhí)行的,而不是繼發(fā)執(zhí)行,改成for就可以解決問題,3個main是分別相隔1秒輸出:
async function main() {
let docs = [{}, {}, {}];
for (let doc of docs) {
await post(doc);
console.log('main');
}
}
function post(){
return new Promise((resolve) => {
setTimeout(resolve, 1000);
});
}
總之,用了async await之后整個人神清氣爽,可以用非常簡潔和優(yōu)雅的代碼實現(xiàn)各種花式異步操作,并且在業(yè)務邏輯復雜的情況下可以不用陷入回調地獄中。不敢說這一定是終極的解決方案,但確實是目前最優(yōu)雅的解決方案!
相關文章
微信小程序通過點擊事件跨頁面?zhèn)鲄⒓癲ata-方法傳參(data-)的示例詳解
在?vue?中,我們可以直接在點擊事件中放入傳遞的參數進行傳參;然而微信小程序中并不適用這樣的寫法,但是微信小程序可以通過自定義屬性從而綁定參數使用,這篇文章主要介紹了微信小程序通過點擊事件跨頁面?zhèn)鲄⒁约癲ata-方法傳參(data-),需要的朋友可以參考下2023-12-12
javascript實現(xiàn)在網頁任意處點左鍵彈出隱藏菜單的方法
這篇文章主要介紹了javascript實現(xiàn)在網頁任意處點左鍵彈出隱藏菜單的方法,設計鼠標事件及css樣式操作的相關技巧,需要的朋友可以參考下2015-05-05
Javascript中定義方法的另類寫法(批量定義js對象的方法)
用了很多的Javascript框架,偶爾也會去看一下框架的源碼,經常會看到這樣的代碼。2011-02-02

