DEV Community

Cover image for 【JS必學】 同步以及非同步 — Callback + Promise + Async/Await
Angus
Angus

Posted on

【JS必學】 同步以及非同步 — Callback + Promise + Async/Await

【JS必學】 同步以及非同步 — Callback + Promise + Async/Await

最近實做了一個縮網址產生器的小作業,更大量的使用 node.js ,因此要來好好面對逃避很久的 Callback + Promise + Async/Await 這個主題,也是為了更深入了解 JavaScript 。

同步與非同步

在說明 Callback + Promise + Async/Await之前先來談談一個概念:同步與非同步。

同步:等待第一個任務完成後才接續下個任務,程式碼一行一行執行。

非同步:和同步執行相反,程式碼不用逐行照著順續執行。

舉個例子:假設目前有行程式碼是需要等待2秒鐘來下載圖片,後面還有其他動作要處理,如果是使用同步執行,就必須等待圖片下載完成後,後面的程式碼才會接續執行,因此等待的這兩秒就會延遲到後面程式碼的運行。

不過如果今天是非同步執行,可以達到下載照片,同時執行後面兩行程式碼的效果。換個例子 JavaScript 向第三方伺服器發出請求,就會牽涉到網路,而網路的速度和穩定性是不可預期的可能會阻塞,所以需要透過非同步的方式來同時處理多組事件的效果,簡單來說非同步執行可以防止執行程序的阻塞。

來看看以下的範例:

同步執行比較好理解,下面的例子中 JavaScript 先執行了 console.log(‘hello’) 後再執行 console.log(‘world’),所以最後在開法者工具會得到先被執行的 hello 和 world。

console.log(“hello”);

console.log(“world”);
Enter fullscreen mode Exit fullscreen mode

再來是不讓耗時任務阻塞的非同步執行:

來說明一下發生了什麼事,hello 和 hola 同時印在開發者工具上,而 world 則等待了 3 秒後才被印出來,程式碼其實是一行接著一行執行的,所以其實在印出 hola 前已經先執行了 setTimeout不過因為是非同步執行, JavaScript 並不會等待三秒後才印出下面的 hola 。

console.log(“hello”);

setTimeout(() => console.log(“world”), 3000);

console.log(“hola”);
Enter fullscreen mode Exit fullscreen mode

Callback (回調)

首先,先來談談 callback ,什麼是 callback ? 我們都知道,JavaScript 中的函數是一級函數,說成白話一點就是他的函數能夠像物件一樣當成參數傳入另一個函數裡,而作為參數傳入另一個函數的函數執行後,他就是一個 callback function ,那聰明的大家應該就會想到,如果參數裡面不斷的使用函數,這個最外層的函數的功能不就可以變得很強大嗎 ?

上面有先簡單介紹過了非同步執行,而 callback function 就是處理非同步執行的方式之一。

常見的例子

除了一開始介紹的 setTimeout 是常見的例子之外,還有:

  • DOM 事件監聽

這裡的callbackFunctionName 是一個函數參數,也就一個callbackFunction,在點擊事件還沒發生之前,這個函數都不會改變,而當未來點擊事件發生時,才會執行這個 callbackFunction,所以簡單來說,就是需要執行 addEventListener 這個函數 callbackFunctionName 才會被執行,這就是回呼函式(callback function)

const btn = document.querySelector(“btn”);

btn.addEventListener(“click”, callbackFunctionName);
Enter fullscreen mode Exit fullscreen mode

目前我們看到了JavaScript 一級函數 — 所有東西都是物件,以及將函數當成物件的 callback function 的強大,那我們現在來看看他的缺點:

在這個例子中可以看到回呼函數裡包著另一個回呼函數,目前只有兩個函數,如果回呼函數有 5個、10個呢 ?

login('username', 'password', (res) => {
    if (res.result === 0) {
        getPosts(res.uid, (posts) => {
            if (posts && posts.length) {
                console.log('你的文章有 ' + posts.length + ' 篇');
            } else {
                console.log('沒有文章');
            }
        });
    } else {
        console.log('登入失敗');
    }
});
Enter fullscreen mode Exit fullscreen mode

就變成經典的 callback hell了

Photo by [Day 14 — 二周目 — 從Promise 昇華到 async/await](https://ithelp.ithome.com.tw/articles/10201420)

我們可以很明顯得看出 callback hell 的劣勢,一層層嵌套的程式碼不利於閱讀與修改,隨著 JavaScript 的不斷迭代,產生了 Promise 以及 asyns await,首先先來介紹 Promise。

Promise

這是 callback function 編寫出的程式碼

使用 Promise 寫出的程式碼,我想大部分的人應該都會覺得 Promise 看起來比較直觀,也比較容易維護吧 ?

Promise 的出現是為了讓我們能夠更簡潔的寫出 callback

Promise — 承諾

看完例子後來說明一下 Promise 的本質,如同字面上翻譯,Promise 就是承諾,承諾什麼呢 ?

Promise的特點

  1. Promise 的狀態不受外部影響:Promise 代表一個非同步操作有三種狀態
  • pending:初始狀態,不是 fulfilled 與 rejected。

  • fulfilled:表示操作成功地完成。

  • rejected:表示操作失敗了。

只有非同步操作的結果可以決定目前處於何種狀態,任何其他操作都無法改變這個狀態。

  1. 一但改變狀態,就不會再改變:Promise 的狀態改變只有兩種可能
  • 從 pending 到 fulfilled

  • 從 pending 到 rejected

只要其中一種狀態發生了,狀態就不會再改變了,會一直保持這個結果,這時就稱為 resolved(已定型)。

接著我們來建立 Promise 實例並實作

一個 Promise 物件是透過 new 關鍵字和它的物件建構子(Constructor)所產生出來的,如同我們上面所說的,這個建構子函式接收一個帶有 resolve 和 reject 兩個參數的函式(executor)

這裡宣告一個新的 Promise 實體(建構子函式)並接受一個函式,這個函式的參數有兩個,分別是resolve 和 reject 。

let promise = new Promise((resolve, reject) => {});
Enter fullscreen mode Exit fullscreen mode

這邊設置一個情境是 uber 司機接受了小明要去機場的訂單,這是一種承諾,司機承諾小明再他去機場,所已有兩種情境,司機順利載小明到機場resolve 和中途發生意外無法履行承諾reject 。

用上述的情境我們看到下面的程式碼,當 sentToAirport 的值是 ture時,代表成功送小明到機場也就是旅行了承諾,因此回傳resolve

let sentToAirport = true;

let promise = new Promise((resolve, reject) => {

if (sentToAirport) {

resolve();

} else {

reject();

}

});
Enter fullscreen mode Exit fullscreen mode

那當小明成功被送到機場後,小明還要劃位,而轉換成程式碼就是做下一步動作的意思,因此可以使用.then() 來執行接續的動作。

這個步驟中 Promise 已經是一個 Promise Object,這個 Object 提供了一個方法( method ),then,意思是當 1 執行完成後,執行 2,2裡須要傳入一個 callback function。

那如果是 rejcet 會回傳什麼呢 ?

這邊我們把 let sentToAirport = true; 改成 false 讓 if else 判斷執行 reject(),此時就會跳出這條錯誤

如果不想要讓開發者工具跳出這段 ‘UnCaught ’就需要在,then 後面加上catch ,

如此一來就可以回調我們自己定義的函數而不是‘UnCaught’ 。

最後來一點 Promise 的總結:

  1. Promise 有效的解決了 callback hell

  2. Promise 提供 resolve() 和 rejcet() 這兩個函數告知 ‘長任務 ’ 執行的進度

  3. then 會在 ‘長任務’ 完成後執行,正常來說 then 裡會寫處理 ‘長任務’ 返回的數據的邏輯

  4. 當 Promise 被rejcet()就需要使用 catch 來捕捉錯誤,避免產生 ‘UnCaught ’ 的狀況。

後記:

已經很久沒有寫部落格了,這次的主題一直很想寫不過一直不斷的拖延,原本要把 Async/Await 也寫進來不過今天的我真的是筋疲力盡了,不過這會讓我明天有個目標,明天一定要寫 Async/Await!!!!!

Top comments (0)