題目轉載來自:github.com/nswbmw/node…node
如何寫出清晰優雅的代碼也是調試重要的一部分,而在過去很長一段時間內,JavaScript 最使人吐槽的就是回調地獄(callback hell)了。先看一段代碼:ios
如今,咱們以十道題鞏固一下前面所學到的 Promise 的知識點。git
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
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 實例。緩存
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.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
})
複製代碼
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.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 捕獲,須要改爲以下其中一種:
由於返回任意一個非 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 能夠接收兩個參數,第 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.resolve()
.then(() => {
console.log('then')
})
process.nextTick(() => {
console.log('nextTick')
})
setImmediate(() => {
console.log('setImmediate')
})
console.log('end')
複製代碼
運行結果:
end
nextTick
then
setImmediate
複製代碼
解釋:process.nextTick
和 promise.then
都屬於 microtask(但 process.nextTick 的優先級大於 promise.then),而 setImmediate 屬於 macrotask,在事件循環的 check 階段執行。事件循環的每一個階段(macrotask)之間都會執行 microtask,以上代碼自己(macrotask)在執行完後會執行一次 microtask。