前端基礎
-JavaScript 的 async / await 到底在幹嘛?前端非同步的簡潔寫法。
甚麼是非同步函數 async / await
saync / await
是 ES8 新增的語法,它允許我們以同步的方式編寫非同步程式碼,讓我們更容易地管理非同步操作,編寫出更清晰、簡潔、易讀的程式碼。
來看一個最簡單的例子。
let p = new Promise((resolve, reject)
=> setTimeout(resolve, 1000, 3));
p.then((x) => console.log(x)) // 一秒後 3
這個 promise 會在 1 秒後解決且數值為 3,但我們必須要把處理結果的函數都放在 .then()
中,有時候很不方便。
而 async / await 就是想解決 .then()
寫起來不方便的問題 👇
async function asyncFn() {
let p = await new Promise((resolve, reject) => {
setTimeout(resolve, 1000, 3);
});
console.log(p);
}
asyncFn();
有沒有覺得更像接近平常寫程式的方式了,而且也不用寫一堆的 .then()
。
簡單解釋上面的程式一就是 async
關鍵字告訴引擎說這個函數有非同步的程式碼,而 await
會等待 promise
的結果,並暫停接下來的程式碼,當結果回傳後才會繼續執行接下來的程式碼。
接下來讓我們更深入講講 async
、await
這兩個關鍵字。
async
async
可以用在聲明式、表達式、箭頭函數以及類的方法上
async function a() {}
let b = async function() {};
let c = async () => {};
class D {
async d() {}
}
且他會返回一個 promise 物件
async function greet(name) {
return `Hello, ${name}!`;
}
console.log(greet('John')) //Promise {<fulfilled>: 'Hello, John!'}
greet('John')
.then(result => console.log(result));
不知道你也沒有想過這個問題,上面說 async
可以減少 .then()
,那為甚麼 async
又要返回 promise 物件呢,是因為在 JS 中,Promise 是用來處理非同步操作的標準方式,而 async 函數本質上也是處理非同步的方式,所以返回的結果自然是 Promise。
await
await 關鍵字只能用在 async function 內部,await 會等待右側的 promise 處理完成並返回處理的結果,如果 await 等待的不是 promise 則直接返回值本身
async function asyncFn() {
let promise = await new Promise((resolve, reject) => {
setTimeout(resolve, 1000, 'promise');
});
let notPromise = await 'notPromise';
console.log(promise, notPromise);
}
asyncFn(); // promise notPromise
補充: JavaScript 在碰到 await 關鍵字時,會記錄在哪裡暫停執行。等到 await 右邊的值可用了,JavaScript 會向消息隊列中推送一個任務,這個任務會恢復異步函數的執行。
所以就算 await 右側不是 promise 函數時也會被當作非同步處理
async function foo() {
console.log(2);
await null;
console.log(4);
}
console.log(1);
foo();
console.log(3);
// 1
// 2
// 3
// 4
這部份如果不太理解可以去看看 event loop。
錯誤處理
要注意的是,await 只能接受成功的 promise,若是處理失敗就會報錯
async function test() {
let err = await Promise.reject('錯誤')
console.log(err)
};
test()
// Uncaught (in promise) 錯誤
必須要使用 try...catch... 的方式去處理錯誤
async function test(condition) {
try {
let p = await new Promise((resolve) => {
if(condition) {
resolve('成功')
} else {
throw new Error('錯誤');
}
});
console.log(p)
} catch(e) {
console.log(e)
}
}
test(true) // 成功
test(false) //Error: 錯誤
await 的限制
此外,await 不能用在巢狀函數(嵌套函數)中使用 👇
async function test() {
function innerFn1() {
return await Promise.resolve(2);
}
const innerFn2 = function() {
return await Promise.resolve(2);
}
const innerfn3 = () => {
return await Promise.resolve(3);
};
}
test()
上面全部都錯誤的使用方式。
和 Promise 的比較
async/await 的優勢在處理多個 promise 組成的 then,有點像 promise 進一步的優化,下面我們來比較兩種寫法。
function fecthSomething(ms, data) { // 模擬非同步
return new Promise((resolve)=>{
setTimeout(() => {
resolve(data);
}, ms);
})
}
/*
* Promise
*/
let p1 = fecthSomething(1000, 'p1');
let p2 = fecthSomething(2000, 'p2');
p1.then((res) => {
console.log(res)
p2.then((res) => {
console.log(res)
console.log('all done')
})
})
// p1
// p2
// 3 秒後 all done
/*
* async/await
*/
async function asyncAwaitFn() {
let res1 = await fecthSomething(1000, 'a1');
let res2 = await fecthSomething(2000, 'a2');
console.log('all done' ,res1, res2);
}
asyncAwaitFn() // 3 秒後 all done a1 a2
有沒有感受到 async 和 promise 的差別呢?
小結
其實 async / await 就像是 promise.then() 的另一種寫法,寫起來就像同步的程式碼,也更簡潔,但處理錯誤的方式要用 try...catch...
不過 async 和 Promise 的寫法其實沒有誰一定比較好,雖然 async 寫起來更簡潔, 但 Promis 有 all()、race()、allSettled() 等好用的寫法, 某些情況下 .then() 可能會比較方便和靈活,所以要使用哪個還是看個人習慣和情境。
那今天就介紹到這篇,希望這幾篇非同步的文章,能讓你更清楚知道 JS 的非同步觀念,我們下篇貼文見~