非同步(異步)程式設計在現代 Web 開發中佔有重要的地位,因為要能夠同時執行所有的函式等,其中以 JavaScript 中底層設計最為適合,這樣的開發方式為我們提供了非同步網路請求操作( GET POST 等)的能力,以及前端能同時確保 UI 保持響應。接下來,我們將開始探討這方面的知識。
首先我們得先了解 JavaScript 是單執行緒的,這代表他一次只能做一件事情,但是歸功於 JavaScript 的事件迴圈的設計機制,讓 JavaScript 能夠非同步的執行,接下來請讓我用一個例子來解釋。
setInterval(()=>{console.info('A', 1)}, 1000);
setInterval(()=>{console.info('B', 2)}, 2000);
console.info('C', 3);
/*
印出
C 3
A 1
B 2
A 1
A 1
B 2
.
.
.
*/
會發現雖然是單執行緒的 JavaScript 印出的其實不會是 A → B → C ,而是 C → A → B 的輸出,這也是因為多虧了其事件迴圈的設計。這是一個蠻簡單的例子,但是其背後的架構還是稍微有點複雜,其中更多的參考資料我是從這裡看到的,它寫得很詳細,還有動圖呢XD 參考資料,也可以直接看影片。
Callback 是 JavaScript 非同步的基礎,它是一個函式,當某個任務完成或發生錯誤時被呼叫。
function doAsyncTask(callback: Function) {
setTimeout(() => {
console.log("Task Done!");
callback();
}, 1000);
}
doAsyncTask(() => console.log("Callback called!"));
/*
印出
Task Done!
Callback called!
*/
也就是說,會先完成 doAsyncTask → 再執行 callback 函式。如果可以,可以參考上面的參考資料,相信會有更加深刻的了解。但是也是會有一個缺點的,當多個非同步函式互相依賴的時候,程式碼就會變得很繁雜而且難以維護,這也被稱之為回呼地獄()。
Promise 是一個表示非同步操作結果的物件。它有主要會有三個狀態:
let myPromise = new Promise<string>((resolve, reject) => {
setTimeout(() => {
resolve("Success");
}, 1000);
});
myPromise
.then(value => console.log(value))
.catch(error => console.error(error));
Promise 是建立一個非同步的物件或函式,依據上面的例子就能讓看似為 myPromise → 成功的話執行 .then( ) ;失敗的話執行 .catch( ),這樣在某方面來說極大的避免了回呼地獄的誕生,而且也能讓下一個介紹的 async await 來做使用唷。
async/await
是基於 Promise 的一種語法糖,使非同步程式碼看起來更像同步程式碼。雖然 Promises 很棒,但是人腦在思考的時候總是需要比較直覺的方式,而同步程式碼恰好滿足這一個觀點。
async function fetchData() {
let response = await fetch('https://api.example.com/data');
let data = await response.json();
console.log(data);
}
透過 async 宣告一個函式 fetchData 為非同步函式,並且內部使用 await 來進行等待,也就是說,會等待 fetch 做完之後收到 response 後,再執行下一行的 response.json() ,用來確保有拿到資料。當然其中 fetch( promise )完成會吐出結果或是拋出異常就需要額外去處理了。
但是直觀的情形下,我們可以很容易的了解這些程式碼的運行過程。