回呼函式 Callbacks、Promises 物件、Async/Await 非同步流程控制

tags: js Async Promises

在製作scrollBlog的js實作時,使用到Async/Await 非同步流程控制,讓畫面捲動往下時,可以捲動畫面往下,設定呈現的時間。
接下來的筆記內容主要參考->彭彭直播

題目的三個技巧都是在做同樣的事情

  • 解決非同步程式方案

什麼時候會使用到非同步流程控制

  • setTimeout與網路連線,較常需要用到非同步的技巧
  • 和資料庫互動的程式(node)

同步程式範例:

  • 按下test按鈕,執行test()
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8" />
  <title>非同步流程控制 Callback、Promise、Async/Await</title>
  <script>
    // 

    function add(n1, n2) {
      

      return n1 + n2;
    }
    function test() {
      let result = add(3, 4);
      console.log(result);
    }

  </script>
</head>

<body>
  <h3>非同步流程控制 Callback、Promise、Async/Await</h3>
  <button onclick="test();">Test</button>
</body>

</html>

非同步的問題起源

  • 希望能後延遲兩秒,再得到加法的結果,並印出

    • 發現return失效(它是return,setTimeout的function,不是result)
  • setTimeout:延遲一段時間,在執行裡面的函式

    • 延遲一段時間,再做加法的程式

<script>
    // 問題的起源:非同步的程式

    function delayedAdd(n1, n2, delayTime) {
      debugger
      // 設定排程,延遲一段時間後執行
      window.setTimeout(function () {
        console.log(2);
        return n1 + n2;
      }, delayTime);
      console.log(1);
    }
    function test() {
      let result = delayedAdd(3, 4, 2000);
      console.log(result);
    }


  </script>

解決非同步的問題

1.回呼函式

  • 增加第四個參數->callback
  • 將加法的結果傳到callback函式中callback(n1 + n2)
  • 原本let result = delayedAdd(3, 4, 2000);delayedAdd回傳值抓不到資料的問題,改為delayedAdd(3, 4, 2000, function (result) { console.log(result);});
    • 將函式傳遞到參數(callback)當中
<script>
    // Callback 回呼函式

    function delayedAdd(n1, n2, delayTime, callback) {
      // 設定排程,延遲一段時間後執行
      window.setTimeout(function () {
        // 延遲一段時間之後,計算加法,呼叫 callback 函式
        callback(n1 + n2);
      }, delayTime);
    }
    function test() {
      delayedAdd(3, 4, 2000, function (result) {
        console.log(result);
      });
    }

  </script>

2.Promise 物件

  • 建立 Promise 物件:new Promise(執行函式)
  • 將setTimeout欲執行的工作,放入promise裡
    • 工作完成,呼叫 resolve 函式,並且把結果透過參數傳遞進去

程式運行流程:

  • 按下test按鈕,呼叫test()->呼叫delayedAdd()
  • 進入delayedAdd(),建立一個新的promise物件(承諾延遲2秒鐘作加法),物件建立完成後,執行的工作會交由另一個執行序,不等待其完成
  • 直接將p回傳,進入promise.then(),會接收2秒鐘之後,如果工作完成,就呼叫resolve(n1 + n2)resolve會接到function (result) -> 結果為7
<script>
    // Promise 物件

    function delayedAdd(n1, n2, delayTime) {
      // 建立 Promise 物件:new Promise(執行函式)
      let p = new Promise(function (resolve, reject) {
        window.setTimeout(function () {
          resolve(n1 + n2); // 工作完成,呼叫 resolve 函式,並且把結果透過參數傳遞進去
        }, delayTime);
      });
      return p;
    }
    function test() {
      let promise = delayedAdd(3, 4, 2000);

      promise.then(function (result) {
        console.log(result);
      });
    }

  </script>

return p = new Promise(function (resolve, reject) {
        window.setTimeout(function () {
          resolve(n1 + n2); // 工作完成,呼叫 resolve 函式,並且把結果透過參數傳遞進去
        }, delayTime);
      });
     
    }
  • reject 處理錯誤的情況

 // Promise 物件

    function delayedAdd(n1, n2, delayTime) {
      // 建立 Promise 物件:new Promise(執行函式)
      return new Promise(function (resolve, reject) {
        window.setTimeout(function () {
          reject(n1 + n2); // 工作完成,呼叫 resolve 函式,並且把結果透過參數傳遞進去
        }, delayTime);
      });
    }
    function test() {
      let promise = delayedAdd(3, 4, 2000);

      promise.then(function (result) {
        console.log(result);
      }).catch(function (error) {
        console.log("error", error)
      })
        ;
    }

Async/Await 語法

  • 背後的運作邏輯和promise是一樣的
  • 前提:一定有一個函式回傳promise物件
  • 增加await後面加上的函式,必須加上promise物件
    • resolve會直接進到result1
    • 在函式中用到await,此函式就要宣告async function
 <script>
    // Async/Await 語法:簡化 Promise 語法
    function delayedAdd(n1, n2, delayTime) {
      // 建立 Promise 物件:new Promise(執行函式)
      return new Promise(function (resolve, reject) {
        window.setTimeout(function () {
          resolve(n1 + n2); // 工作完成,呼叫 resolve 函式,並且把結果透過參數傳遞進去
        }, delayTime);
      });
    }
    async function test() {
      // 分別等待多個 Promise 完成後才繼續動作
      let result1 = await delayedAdd(3, 4, 2000);
      console.log(result1)
      // let result2 = await delayedAdd(2, 3, 3000);
      // let answer = result1 * result2;
      // console.log(answer);
    }

  </script>

試做2次的延遲加法

1.使用promise

  • let promise1 = delayedAdd(3, 4, 2000);延遲2秒, let promise2 = delayedAdd(2, 3, 3000);3+4; 延遲3秒,2+3
  • 同時等待多個 Promise(大寫P)->Promise.all([promise1, promise2]).then():等到all裡面的promise都執行完,再執行.then()
function delayedAdd(n1, n2, delayTime) {
      // 建立 Promise 物件:new Promise(執行函式)
      return new Promise(function (resolve, reject) {
        window.setTimeout(function () {
          resolve(n1 + n2); // 工作完成,呼叫 resolve 函式,並且把結果透過參數傳遞進去
        }, delayTime);
      });
    }
    function test() {
      let promise1 = delayedAdd(3, 4, 2000);
      let promise2 = delayedAdd(2, 3, 3000);
      // 同時等待多個 Promise 都完成之後,才繼續工作
      Promise.all([promise1, promise2]).then(function (results) {
        let answer = results.reduce(function (total, value) {
          return total * value;
        });
        console.log(answer);
      });
    }

2.使用Async/Await 語法

<script>
    // Async/Await 語法:簡化 Promise 語法

      function delayedAdd(n1, n2, delayTime) {
        // 建立 Promise 物件:new Promise(執行函式)
        return new Promise(function (resolve, reject) {
          window.setTimeout(function () {
            resolve(n1 + n2); // 工作完成,呼叫 resolve 函式,並且把結果透過參數傳遞進去
          }, delayTime);
        });
      }
      async function test() {
        // 分別等待多個 Promise 完成後才繼續動作
        let result1 = await delayedAdd(3, 4, 2000);
        let result2 = await delayedAdd(2, 3, 3000);
        let answer = result1 * result2;
        console.log(answer);
      }

  </script>

使用 try catch 做錯誤和例外處理

在 async function 中,使用try catch來處理
以上個為例:

async function test() {
    try {
        // 分別等待多個 Promise 完成後才繼續動作
        let result1 = await delayedAdd(3, 4, 2000);
        let result2 = await delayedAdd(2, 3, 3000);
        let answer = result1 * result2;
        console.log(answer);  
        } catch (err) {
        console.log(err)
    } // TypeError: Failed to fetch
      }


參考資料:
loupe

回呼函式 Callbacks、Promises 物件、Async/Await 非同步流程控制 - 彭彭直播

從 JavaScript Promise 到 Async Await


  • promise
    • 若需要明確界定做作之間的順序相依性,就要使用promise。若不在乎何時做完,也不在乎某function之前要先完成哪些function,就不需要使用promise,因為就讓functions各自去執行即可。
    • 通常使用promise時,會搭配使用箭頭函式,因為promise跟箭頭函式都同樣是ES6的新功能,可以使用promise就代表可以使用箭頭函式了。
Select a repo