JavaScript Promises, async/await

fnmain發表於2022-12-07

這篇部落格不是講如何使用 Promise,是一個學習總結。是我自己對 Promise 的理解。

new Promise() 的時候,傳一個 executor 給 Promise.

let promise = new Promise(function(resolve, reject) {
  // this function will executes immediately
})

這個函式會立刻被執行,在 executor 裡面呼叫了 resolve 之後, Promise 變為 fulfilled,在 executor 裡面呼叫了 reject 之後,Promise 變為 rejected。如果 resolve 和 reject 都還沒被呼叫,Promise 的狀態是 pending(等待結果)。.then/.catch/.finally都是非同步的,JavaScript 是單執行緒模型,所有的非同步任務都將被放到任務佇列裡,等主執行緒的所有的程式碼執行完然後才執行任務佇列裡的任務。JavaScript 是單執行緒,也就是所有的非同步程式碼都會等到 JS 檔案裡面的程式碼都執行完了才執行。

setTimeout() 是非同步的,不會阻塞函式執行,所以 let promise 立刻得到了 Promise 物件。Promise 的 state 和 result 都是內部的,不能直接訪問。要拿到 result, 就要使用 .then 或者 .catch 方法,傳一個接受結果的函式給他們。Promise.resolve() 就相當於 new Promise(resolve => resolve())Promise.reject() 相當於是 new Promise((resolve, reject) => reject())

.then.catch 都是返回一個 Promise 物件,而 Promise 物件有 then 和 catch 方法,所以可以鏈起來。這個 undefined 是 console.log(r) 的輸出。

在 then 裡面也可以 return new Promise(...)1。對於錯誤處理,executor 外部有一個隱式的 try...catch,也就是說 executor 執行流的異常會被後來最近的 .catch() 呼叫捕獲到,沒有捕獲的的錯誤最後會被拋到 window,或者全域性。值得注意的是 .catch() 處理異常以後也會返回一個 Promise 物件。

async 意味著函式一定返回一個 Promise,如果函式正常 return,return 的結果在一個 resolved Promise 裡面。.then() 裡面可以返回一個 Promise,在 async 函式里面也可以顯式的返回一個 Promise。

await 不是單純的等待,不是阻塞的等待,literally suspends the function execution,實際上是掛起這個函式的執行,或者說暫停這個函式的執行,等 Promise settle 也是就是等 Promise 有結果之後,然後 resume(恢復)執行。這就有點像是生成器的 yield,從上次打斷的地方恢復執行。await 掛起/暫停的時候會CPU執行其他的非同步任務,await 有等待的意思,不過實際上還有讓步,會將執行權讓給其他的任務,等其他的任務執行完或者其他的任務讓步以後,然後繼續之前 await 的地方執行。具體會執行到哪個非同步任務,得看當時任務佇列裡面有什麼任務在等待執行。

Let’s emphasize: await literally suspends the function execution until the promise settles, and then resumes it with the promise result. That doesn’t cost any CPU resources, because the JavaScript engine can do other jobs in the meantime: execute other scripts, handle events, etc.

await 會去呼叫.then(),獲取 Promise 的結果。async/await 使得呼叫非同步函式,不需要寫回撥了,也不需要一直 .then,非同步程式碼就像普通的程式碼一樣。

async/await 是一個比較通用的關於協程的關鍵字。這其實就是 JavaScript 的協程,可以實現單執行緒的併發。協程簡單來說就是一個會記住狀態的函式,就像生成器一樣,重新進入函式的時候會從上次離開的地方繼續。好像 async/await 實現的協程都是 stackless 的協程,狀態是用閉包或者一個物件來儲存。按照我目前拙劣的理解,Promise 的狀態就是一個物件,而閉包實際上也是可以用 OOP 的手段來做的。

Funny Promise, async/await

下面是一個好玩的例子,用 Promise 重寫使用 await 的斐波那契數列。

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms))
}

async function async_fib(n) {
  if (n == 0)
    return 0
  else if (n == 1)
    return 1
  
  await sleep(1000)

  return await async_fib(n - 1) + await async_fib(n - 2)
}

function fib(n) {
  if (n == 0) {
    return 0
  } else if (n == 1) {
    return 1
  }
  
  return sleep(1000)
          .then(() => {
            const f = fib(n-1);
            if (f.then)
              return f.then(left => {
                  const g = fib(n-2)
                  if (g.then)
                    return g.then(right => {
                      return left + right
                    })
                  else
                    return left + g
                });
            else
              return f
          })
}

fib(3).then(res => console.log(res))

相關文章