【轉】Promise 必知必會(十道題)

舞動乾坤發表於2017-11-09

原文轉自:https://zhuanlan.zhihu.com/p/30797777

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

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

題目一

const promise = new Promise((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 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 1000)
})
const promise2 = promise1.then(() => {
  throw new Error('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 = new Promise((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)
    return 2
  })
  .catch((err) => {
    return 3
  })
  .then((res) => {
    console.log(res)
  })
複製程式碼

執行結果:

1
2
複製程式碼

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

題目五

const promise = new Promise((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(() => {
    return new Error('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 捕獲,需要改成其中一種:

  1. return Promise.reject(new Error('error!!!'))
  2. throw new Error('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(function tick () {
  console.log('tick')
  process.nextTick(tick)
})
複製程式碼

題目八

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)
複製程式碼

執行結果:

1
複製程式碼

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

題目九

Promise.resolve()
  .then(function success (res) {
    throw new Error('error')
  }, function fail1 (e) {
    console.error('fail1: ', e)
  })
  .catch(function fail2 (e) {
    console.error('fail2: ', e)
  })
複製程式碼

執行結果:

fail2: Error: error
    at success (...)
    at ...
複製程式碼

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

Promise.resolve()
  .then(function success1 (res) {
    throw new Error('error')
  }, function fail1 (e) {
    console.error('fail1: ', e)
  })
  .then(function success2 (res) {
  }, function fail2 (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
then
setImmediate
複製程式碼

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


相關文章