Promise 必知必會(十道題)

  • 2019 年 11 月 24 日
  • 筆記

前言

Promise 想必大家都十分熟悉,想想就那麼幾個 api,可是你真的了解 Promise 嗎?本文根據 Promise 的一些知識點總結了十道題,看看你能做對幾道。

以下 promise 均指代 Promise 實例,環境是 Node.js。

題目一

    const promise = newPromise((resolve, reject) => {        console.log(1)        resolve()        console.log(2)      })      promise.then(() => {        console.log(3)      })      console.log(4)

運行結果:

    1      2      4      3

解釋:Promise 構造函數是同步執行的,promise.then 中的函數是非同步執行的。

題目二

    const promise1 = newPromise((resolve, reject) => {        setTimeout(() => {          resolve('success')        }, 1000)      })      const promise2 = promise1.then(() => {        thrownewError('error!!!')      })        console.log('promise1', promise1)      console.log('promise2', promise2)        setTimeout(() => {        console.log('promise1', promise1)        console.log('promise2', promise2)      }, 2000)

運行結果:

    promise1 Promise { <pending> }      promise2 Promise { <pending> }      (node:50928) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: error!!!      (node:50928) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.      promise1 Promise { 'success' }      promise2 Promise {        <rejected> Error: error!!!          at promise.then (...)          at <anonymous> }

解釋:promise 有 3 種狀態:pending、fulfilled 或 rejected。狀態改變只能是 pending->fulfilled 或者 pending->rejected,狀態一旦改變則不能再變。上面 promise2 並不是 promise1,而是返回的一個新的 Promise 實例。

題目三

    const promise = newPromise((resolve, reject) => {        resolve('success1')        reject('error')        resolve('success2')      })        promise        .then((res) => {          console.log('then: ', res)        })        .catch((err) => {          console.log('catch: ', err)        })

運行結果:

    then: success1

解釋:構造函數中的 resolve 或 reject 只有第一次執行有效,多次調用沒有任何作用,呼應程式碼二結論:promise 狀態一旦改變則不能再變。

題目四

    Promise.resolve(1)        .then((res) => {          console.log(res)          return2        })        .catch((err) => {          return3        })        .then((res) => {          console.log(res)        })

運行結果:

    1      2

解釋:promise 可以鏈式調用。提起鏈式調用我們通常會想到通過 return this 實現,不過 Promise 並不是這樣實現的。promise 每次調用 .then 或者 .catch 都會返回一個新的 promise,從而實現了鏈式調用。

題目五

    const promise = newPromise((resolve, reject) => {        setTimeout(() => {          console.log('once')          resolve('success')        }, 1000)      })        const start = Date.now()      promise.then((res) => {        console.log(res, Date.now() - start)      })      promise.then((res) => {        console.log(res, Date.now() - start)      })

運行結果:

    once      success 1005      success 1007

解釋:promise 的 .then 或者 .catch 可以被調用多次,但這裡 Promise 構造函數只執行一次。或者說 promise 內部狀態一經改變,並且有了一個值,那麼後續每次調用 .then 或者 .catch 都會直接拿到該值。

題目六

    Promise.resolve()        .then(() => {          returnnewError('error!!!')        })        .then((res) => {          console.log('then: ', res)        })        .catch((err) => {          console.log('catch: ', err)        })

運行結果:

    then: Error: error!!!          at Promise.resolve.then (...)          at ...

解釋:.then 或者 .catch 中 return 一個 error 對象並不會拋出錯誤,所以不會被後續的 .catch 捕獲,需要改成其中一種:

    returnPromise.reject(newError('error!!!'))      thrownewError('error!!!')

因為返回任意一個非 promise 的值都會被包裹成 promise 對象,即 return new Error('error!!!') 等價於 return Promise.resolve(new Error('error!!!'))

題目七

    const promise = Promise.resolve()        .then(() => {          return promise        })      promise.catch(console.error)

運行結果:

    TypeError: Chaining cycle detected for promise #<Promise>          at <anonymous>          at process._tickCallback (internal/process/next_tick.js:188:7)          at Function.Module.runMain (module.js:667:11)          at startup (bootstrap_node.js:187:16)          at bootstrap_node.js:607:3

解釋:.then.catch 返回的值不能是 promise 本身,否則會造成死循環。類似於:

    process.nextTick(functiontick () {        console.log('tick')        process.nextTick(tick)      })

題目八

    Promise.resolve(1)        .then(2)        .then(Promise.resolve(3))        .then(console.log)

運行結果:

    1

解釋:.then 或者 .catch 的參數期望是函數,傳入非函數則會發生值穿透。

題目九

    Promise.resolve()        .then(functionsuccess (res) {          thrownewError('error')        }, functionfail1 (e) {          console.error('fail1: ', e)        })        .catch(functionfail2 (e) {          console.error('fail2: ', e)        })

運行結果:

    fail2: Error: error          at success (...)          at ...

解釋:.then 可以接收兩個參數,第一個是處理成功的函數,第二個是處理錯誤的函數。.catch.then 第二個參數的簡便寫法,但是它們用法上有一點需要注意:.then 的第二個處理錯誤的函數捕獲不了第一個處理成功的函數拋出的錯誤,而後續的 .catch 可以捕獲之前的錯誤。當然以下程式碼也可以:

    Promise.resolve()        .then(functionsuccess1 (res) {          thrownewError('error')        }, functionfail1 (e) {          console.error('fail1: ', e)        })        .then(functionsuccess2 (res) {        }, functionfail2 (e) {          console.error('fail2: ', e)        })

題目十

    process.nextTick(() => {        console.log('nextTick')      })      Promise.resolve()        .then(() => {          console.log('then')        })      setImmediate(() => {        console.log('setImmediate')      })      console.log('end')

運行結果:

    end      nextTick      thensetImmediate

解釋:process.nextTickpromise.then 都屬於 microtask,而 setImmediate 屬於 macrotask,在事件循環的 check 階段執行。事件循環的每個階段(macrotask)之間都會執行 microtask,事件循環的開始會先執行一次 microtask。


本文作者為石墨文檔的 nswbmw 同學。