非同步三兄弟


技術不難,困難的是要解決的問題是甚麼

非同步舉例:網路連線、排程、setTimeout、node.js 跟資料庫互動的程式、檔案存取/讀取、資料庫連線

非同步程式,不易做流程控制(那些先執行那些後執行)。
解決方式:
一、call back
二、promise 物件
三、Async Await

setTimeout 會延遲一段時間再跑裡面的函式,時間單位為千分之一秒,寫 2000 表示 2 秒鐘。

想做到的效果 : 延遲兩秒做加法,並將結果印出來。但印出的卻是 undefined。
因為這是非同步的程式,所以設定排程後不會原地等待兩秒,他會往下走然後回傳

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>非同步</title>
  <script>
    // 問題起源:非同步程式
    function delayedAdd(n1, n2, delayTime) {
      window.setTimeout(function() {
        return n1 + n2
      }, delayTime)
    }
    function test() {
      let result = delayedAdd(3, 4, 2000)
      console.log(result)
    }
  </script>
</head>
<body>
  <h3>非同步控制流程</h3>
  <button onclick="test()" style="padding: 10px 30px;">test</button>
</body>
</html>

點按鈕 => 1 先印出然後印出 undefined 兩秒後再印出 2 => 需求失敗
return 失效,因為寫在 function 內,他是 return function 不是 return delayedAdd

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

一、cb

// cb function
function delayedAdd(n1, n2, delayTime, cb) {
  // 設定排程,延遲一段時間後執行
  window.setTimeout(function() {
    // 延遲一段時間後,計算加法,呼叫 cb 函式
    cb(n1 + n2)
  }, delayTime)
}
function test() {
 // 這樣寫表示從 delayedAdd 回傳值抓資料 delayedAdd(3, 4, 2000) 但回傳值只會拿到 undefined
  delayedAdd(3, 4, 2000, function(result) {
    // 函式會傳到 cb 參數
    console.log(result)
  })
}

二、promise
瀏覽器承諾幫你做一個工作,這個工作是延遲兩秒做加法,做完加法告訴人家我做完了並把結果丟進去,如果工作出狀況,就呼叫 reject

先放掉疑問,看流程 !! 非同步程式不等待

  1. 點按鈕呼叫 test()
  2. 呼叫 delayedAdd(3, 4, 2000)
  3. 建立新的 promise 物件 new Promise( (resolve, reject) => {略})
  4. 執行 promise 的工作交給另外的執行區,瀏覽器會請另外的 cpu 做裡面的工作
  5. 回傳 promise 物件
  6. promise.then(函式),函式接收兩秒後工作完成呼叫的 resolve()
// promise 物件
function delayedAdd(n1, n2, delayTime) {
  // 建立 promise 物件: new Promise(執行函式),將要做的工作放入執行函式中
  // 執行函式裡面會有兩個參數 resolve reject,第一個參數表示成功之後要呼叫的函式,第二個參數表示執行過程中遇到的任何問題會對應到 catch
  let p = new Promise( (resolve, reject) => {
    window.setTimeout(function() {
      resolve(n1 + n2) // 工作完成,呼叫 resolve 函式,並把結果透過參數傳遞進去
    }, delayTime)
  })
  return p // 傳遞 promise 物件。建立工作後立刻回傳,裡面的工作還在等待不管他
}
function test() {
  let promise = delayedAdd(3, 4, 2000) // 拿到 promise 物件
  promise.then(result => console.log(result)) // 用 .then 拿結果,結果是工作區 resolve 函式過來的
}

簡化上述代碼:建立完 promise 物件直接 return

    function delayedAdd(n1, n2, delayTime) {
      // 建立完 promise 物件直接 return
      return new Promise( (resolve, reject) => {
        window.setTimeout(function() {
          resolve(n1 + n2) // 工作完成,呼叫 resolve 函式,並把結果透過參數傳遞進去
        }, delayTime)
      })
    }

resolve 的呼叫會對應到 .then()
reject 的呼叫會對應到 .catch(),會用他做錯誤處理。例如:網路連線斷掉,就用 catch 做錯誤的狀況。

function delayedAdd(n1, n2, delayTime) {
  // 建立完 promise 物件直接 return
  return new Promise( (resolve, reject) => {
    window.setTimeout(function() {
      reject(n1 + n2) // 工作完成,呼叫 resolve 函式,並把結果透過參數傳遞進去
    }, delayTime)
  })
}
function test() {
  let promise = delayedAdd(3, 4, 2000) // 拿到 promise 物件
  promise.then(result => console.log(result)) // 用 .then 拿結果,結果是工作區 resolve 函式過來的
         .catch(error => console.log('error', error))
}

先不要想為甚麼要這樣,直接看他怎麼運作。等練習熟練再看為甚麼

三、async await

前提:await 函式必須要 return promise 物件

真正改善部分是在呼叫的地方。原本是將工作區的結果,透過 reslove() 傳到 .then。現在,透過語法糖讓他閱讀上更容易。

await 一個回傳 promise 的函式,如果 awiat 後面接的不是 promise 他就是一個錯誤的語法。在函式中使用 await,必須在函式外面宣告 async。

錯誤處理:try catch

// async / await:簡化 promise 語法 (背後運作邏輯同 promise )

function delayedAdd(n1, n2, delayTime) {
  // 建立完 promise 物件直接 return
  return new Promise( (resolve, reject) => {
    window.setTimeout(function() {
      resolve(n1 + n2) // 工作完成,呼叫 resolve 函式,並把結果透過參數傳遞進去
    }, delayTime)
  })
}
async function test() {
  let result = await delayedAdd(3, 4, 2000) // await 一個 promise,resolve 的結果會進入 result 裡面。在 await 的地方等待,等 result 回來。不管後面寫甚麼都會在這邊被卡住
  console.log(result)
  console.log('hi')
}

兩次網路連線抓不同資源、兩次延遲的加法,再用這個結果做其他事情。要怎麼辦呢 ?

另一個需求

延遲兩秒做 3 + 4 延遲三秒做 2 + 3,等到加法都做完再相乘 => 7 * 5

// 卡住了
function test() {
  let promise1 = delayedAdd(3, 4, 2000)
  let promise2 = delayedAdd(2, 3, 3000)

  promise1.then(result => console.log(result))
  promise2.then(result => console.log(result))
}

Promise.all([所有希望完成的 promise 都放入這個陣列]),

function test() {
  let promise1 = delayedAdd(3, 4, 2000)
  let promise2 = delayedAdd(2, 3, 3000)
  // 多個 promise 都完成後才繼續工作
  Promise.all([promise1, promise2]).then(function(results) {
    console.log(results) // 會將所有的結果都送出 => [7, 5]
  })
}
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) // 35
  })
}

同樣的邏輯用 async await 完成

// async / await:簡化 promise 語法 (背後運作邏輯同 promise )

function delayedAdd(n1, n2, delayTime) {
  // 建立完 promise 物件直接 return
  return new Promise( (resolve, reject) => {
    window.setTimeout(function() {
      resolve(n1 + n2) // 工作完成,呼叫 resolve 函式,並把結果透過參數傳遞進去
    }, delayTime)
  })
}
async function test() {
 // 比較久,先等 result1 再等 result2
  let result1 = await delayedAdd(3, 4, 2000)
  let result2 = await delayedAdd(2, 3, 3000)
  let answer = result1 * result2
  console.log(answer)
}

初期:不要想太多,語法流程是怎麼就是怎樣

資料參考:
回呼函式 Callbacks、Promises 物件、Async/Await 非同步流程控制 - 彭彭直播 at 2019/04/07

#非同步 #Callback #Promise #async-await






Related Posts

Day04 - CSS內距、邊框、輪廓與邊界範圍

Day04 - CSS內距、邊框、輪廓與邊界範圍

MTR04_0621

MTR04_0621

MTR04_0619

MTR04_0619






Comments