【Promise 】必知必會經典題

題目轉載來自:github.com/nswbmw/node…node

如何寫出清晰優雅的代碼也是調試重要的一部分,而在過去很長一段時間內,JavaScript 最使人吐槽的就是回調地獄(callback hell)了。先看一段代碼:ios

如今,咱們以十道題鞏固一下前面所學到的 Promise 的知識點。git

題目一: Promise構造函數

const promise = new Promise((resolve, reject) => {
  console.log(1)
  resolve()
  console.log(2)
})
promise.then(() => {
  console.log(3)
})
console.log(4)
複製代碼

運行結果:github

1
2
4
3
複製代碼

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

題目二: Promise狀態機

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)
複製代碼

運行結果:promise

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 實例。緩存

題目三: 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)
  })
複製代碼

運行結果:bash

then: success1
複製代碼

解釋:構造函數中的 resolve 或 reject 只有在第 1 次執行時有效,屢次調用沒有任何做用,再次印證代碼二的結論:promise 狀態一旦改變則不能再變。app

再看兩個例子:異步

const promise = new Promise((resolve, reject) => {
  console.log(1)
  return Promise.reject(new Error('haha'))
})
promise.then((res) => {
  console.log(2, res)
}).catch((err) => {
  console.error(3, err)
})
console.log(4)
console.log(promise)
複製代碼

運行結果:

1
4
Promise { <pending> }
(node:22493) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: haha
(node:22493) [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.
複製代碼
const promise = new Promise((resolve, reject) => {
  console.log(1)
  throw new Error('haha')
})
promise.then((res) => {
  console.log(2, res)
}).catch((err) => {
  console.error(3, err)
})
console.log(4)
console.log(promise)
複製代碼

運行結果:

1
4
Promise {
  <rejected> Error: haha
    at Promise (/Users/nswbmw/Desktop/test/app.js:6:9)
    ...
3 Error: haha
    at Promise (/Users/nswbmw/Desktop/test/app.js:6:9)
    ...
複製代碼

解釋:構造函數內只能經過調用 resolve(pending->fullfiled) 或者 reject(pending->rejected) 或者 throw 一個 error(pending->rejected) 改變狀態。因此第一個例子的 promise 狀態是 pending,也就不會調用 .then/.catch。

題目四: 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,從而能夠實現鏈式調用。

Axios就是經過鏈式調用來實現攔截器的;原理以下:

/** 簡單解釋鏈式調用,定義一個promiseRequest */
let requestPromise = function () {
  let chain = [{
    resolved: () => { console.log('xhr'); return 'xhr'},
    rejected: () => 'rejected'
  }]

  let requestInters = ['request1', 'request2', 'request3']
  requestInters.forEach(request => {
    chain.unshift({
      resolved: () => { console.log(request); return request},
      rejected: () => `${err}${request}`
    })
  })

  let responseInters = ['response1', 'response2', 'response3']
  responseInters.forEach(response => {
    chain.push({
      resolved: () => { console.log(response); return response},
      rejected: () => `${err}${response}`
    })
  })

  let promise = Promise.resolve('config')

  while (chain.length) {
    let { resolved, rejected } = chain.shift()
    // 精華都是在這句

    promise = promise.then(resolved, rejected)
  }
  // return promise調用鏈
  // promise.then(resolved1, rejected1).then(resolved2, rejected2).then(resolved2, rejected3).then() ....
  return promise
}

requestPromise().then(res => {

  console.log(res)
  // request3
  // request2
  // request1
  // xhr
  // response1
  // response2
  // response3
  // response3
})
複製代碼

題目五: 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緩存中是頗有用的,好比app啓動時能夠暫存promise,在沒有返回時,將promise直接給其餘調用方,而不是每次調用都去new 一個新的promise.

題目六: Promise錯誤的正常拋出

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!!!'))

題目七: 當心Promise死循環

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值穿透

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

運行結果:

1
複製代碼

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

題目九:Promise錯誤捕獲

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 能夠接收兩個參數,第 1 個是處理成功的函數,第 2 個是處理錯誤的函數。.catch 是 .then 第 2 個參數的簡便寫法,可是在用法上有一點須要注意:.then 的第 2 個處理錯誤的函數(fail1)捕獲不了第 1 個處理成功的函數(success)拋出的錯誤,然後續的 .catch 方法(fail2)能夠捕獲以前的錯誤。固然,如下代碼也能夠:

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)
  })
複製代碼

題目十: Promise與事件循環

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

運行結果:

end
nextTick
then
setImmediate
複製代碼

解釋process.nextTickpromise.then 都屬於 microtask(但 process.nextTick 的優先級大於 promise.then),而 setImmediate 屬於 macrotask,在事件循環的 check 階段執行。事件循環的每一個階段(macrotask)之間都會執行 microtask,以上代碼自己(macrotask)在執行完後會執行一次 microtask。

相關文章
相關標籤/搜索