首發地址:sau交流學習社區
一、前言
什么是promise?promsie的核心是什么?promise如何解決回調地獄的?等問題
1、什么是promise?promise是表示異步操作的最終結果;可以用來解決回調地獄和並發IO操作的問題
A promise represents the eventual result of an asynchronous operation.
2、promise 的核心是什么?promise的核心就是鏈式調用
3、采用什么方法可以實現鏈式調用?通過使用then的方法,then方法是用來注冊在這個Promise狀態確定后的回調,很明顯,then方法需要寫在原型鏈上。
4、promise是如何解決回調地獄的問題?(1)如果一個promise返回的是一個promise,會把這個promise傳遞結果傳遞到下一次的then中;(2)如果一個promise返回的是一個普通的值,會把這個普通值作為下一次then的成功回調結果;(3)如果當前promise失敗了,會走下一個then的回調函數;(4)如果then不返回值,就會有一個默認值為undefined,作為普通值,會作為下一個then的成功回調;(5)catch是錯誤沒有處理的情況才會執行;(6)then中可以不寫東西
二、promise的標准解讀
1、只有一個then
方法,沒有catch
,race
,all
等方法,甚至沒有構造函數;
Promise標准中僅指定了Promise對象的then
方法的行為,其它一切我們常見的方法/函數都並沒有指定,包括catch
,race
,all
等常用方法,甚至也沒有指定該如何構造出一個Promise對象,另外then也沒有一般實現中(Q, $q等)所支持的第三個參數,一般稱onProgress
2、then
方法返回一個新的Promise;
Promise的then
方法返回一個新的Promise,而不是返回this,此處在下文會有更多解釋
promise2 = promise1.then(alert)
promise2 != promise1 // true
3、不同Promise的實現需要可以相互調用(interoperable)
4、Promise的初始狀態為pending,它可以由此狀態轉換為fulfilled(本文為了一致把此狀態叫做resolved)或者rejected,一旦狀態確定,就不可以再次轉換為其它狀態,狀態確定的過程稱為settle
三、實現一個promise
1、構造函數
因為標准並沒有指定如何構造一個Promise對象,所以我們同樣以目前一般Promise實現中通用的方法來構造一個Promise對象,也是ES6原生Promise里所使用的方式,即:
// Promise構造函數接收一個executor函數,executor函數執行完同步或異步操作后,調用它的兩個參數resolve和reject var promise = new Promise(function(resolve, reject) { /* 如果操作成功,調用resolve並傳入value 如果操作失敗,調用reject並傳入reason */ })
我們先實現構造函數的框架如下:
function Promise(executor) { var self = this self.status = 'pending' // Promise當前的狀態 self.data = undefined // Promise的值 self.onResolvedCallback = [] // Promise resolve時的回調函數集,因為在Promise結束之前有可能有多個回調添加到它上面 self.onRejectedCallback = [] // Promise reject時的回調函數集,因為在Promise結束之前有可能有多個回調添加到它上面 executor(resolve, reject) // 執行executor並傳入相應的參數 }
上面的代碼基本實現了Promise構造函數的主體,但目前還有兩個問題:
1、我們給executor函數傳了兩個參數:resolve和reject,這兩個參數目前還沒有定義
2、executor有可能會出錯(throw),類似下面這樣,而如果executor出錯,Promise應該被其throw出的值reject:
new Promise(function(resolve, reject) { throw 2 })
所以我們需要在構造函數里定義resolve和reject這兩個函數:
function Promise(executor) { var self = this self.status = 'pending' // Promise當前的狀態 self.data = undefined // Promise的值 self.onResolvedCallback = [] // Promise resolve時的回調函數集,因為在Promise結束之前有可能有多個回調添加到它上面 self.onRejectedCallback = [] // Promise reject時的回調函數集,因為在Promise結束之前有可能有多個回調添加到它上面 function resolve(value) { // TODO } function reject(reason) { // TODO } try { // 考慮到執行executor的過程中有可能出錯,所以我們用try/catch塊給包起來,並且在出錯后以catch到的值reject掉這個Promise executor(resolve, reject) // 執行executor } catch(e) { reject(e) } }
有人可能會問,resolve和reject這兩個函數能不能不定義在構造函數里呢?考慮到我們在executor函數里是以resolve(value)
,reject(reason)
的形式調用的這兩個函數,而不是以resolve.call(promise, value)
,reject.call(promise, reason)
這種形式調用的,所以這兩個函數在調用時的內部也必然有一個隱含的this,也就是說,要么這兩個函數是經過bind后傳給了executor,要么它們定義在構造函數的內部,使用self來訪問所屬的Promise對象。所以如果我們想把這兩個函數定義在構造函數的外部,確實是可以這么寫的:
function resolve() { // TODO } function reject() { // TODO } function Promise(executor) { try { executor(resolve.bind(this), reject.bind(this)) } catch(e) { reject.bind(this)(e) } }
但是眾所周知,bind也會返回一個新的函數,這么一來還是相當於每個Promise對象都有一對屬於自己的resolve和reject函數,就跟寫在構造函數內部沒什么區別了,所以我們就直接把這兩個函數定義在構造函數里面了。不過話說回來,如果瀏覽器對bind的所優化,使用后一種形式應該可以提升一下內存使用效率。
另外我們這里的實現並沒有考慮隱藏this上的變量,這使得這個Promise的狀態可以在executor函數外部被改變,在一個靠譜的實現里,構造出的Promise對象的狀態和最終結果應當是無法從外部更改的。
接下來,我們實現resolve和reject這兩個函數
function Promise(executor) { // ... function resolve(value) { if (self.status === 'pending') { self.status = 'resolved' self.data = value for(var i = 0; i < self.onResolvedCallback.length; i++) { self.onResolvedCallback[i](value) } } } function reject(reason) { if (self.status === 'pending') { self.status = 'rejected' self.data = reason for(var i = 0; i < self.onRejectedCallback.length; i++) { self.onRejectedCallback[i](reason) } } } // ... }
基本上就是在判斷狀態為pending之后把狀態改為相應的值,並把對應的value和reason存在self的data屬性上面,之后執行相應的回調函數,邏輯很簡單,這里就不多解釋了。
2、then方法
then方法是用來注冊這個promise確定狀態后的回調,then方法是需要寫在原型鏈上。
自然約束:then方法會返回一個Promise,關於這一點,Promise/A+標准並沒有要求返回的這個Promise是一個新的對象,但在Promise/A標准中,明確規定了then要返回一個新的對象,目前的Promise實現中then幾乎都是返回一個新的Promise(https://promisesaplus.com/differences-from-promises-a#point-5)對象,所以在我們的實現中,也讓then返回一個新的Promise對象。
下面我們來實現then方法:
// then方法接收兩個參數,onResolved,onRejected,分別為Promise成功或失敗后的回調 Promise.prototype.then = function(onResolved, onRejected) { var self = this var promise2 // 根據標准,如果then的參數不是function,則我們需要忽略它,此處以如下方式處理 onResolved = typeof onResolved === 'function' ? onResolved : function(v) {} onRejected = typeof onRejected === 'function' ? onRejected : function(r) {} if (self.status === 'resolved') { return promise2 = new Promise(function(resolve, reject) { }) } if (self.status === 'rejected') { return promise2 = new Promise(function(resolve, reject) { }) } if (self.status === 'pending') { return promise2 = new Promise(function(resolve, reject) { }) } }
Promise總共有三種可能的狀態,我們分三個if塊來處理,在里面分別都返回一個new Promise。
根據標准,我們知道,對於如下代碼,promise2的值取決於then里面函數的返回值:
promise2 = promise1.then(function(value) { return 4 }, function(reason) { throw new Error('sth went wrong') })
如果promise1被resolve了,promise2的將被`4` resolve,如果promise1被reject了,promise2將被`new Error('sth went wrong')` reject,更多復雜的情況不再詳述。
3、完整的promise
try {
module.exports = Promise
} catch (e) {}
function Promise(executor) {
var self = this
self.status = 'pending'
self.onResolvedCallback = []
self.onRejectedCallback = []
function resolve(value) {
if (value instanceof Promise) {
return value.then(resolve, reject)
}
setTimeout(function() { // 異步執行所有的回調函數
if (self.status === 'pending') {
self.status = 'resolved'
self.data = value
for (var i = 0; i < self.onResolvedCallback.length; i++) {
self.onResolvedCallback[i](value)
}
}
})
}
function reject(reason) {
setTimeout(function() { // 異步執行所有的回調函數
if (self.status === 'pending') {
self.status = 'rejected'
self.data = reason
for (var i = 0; i < self.onRejectedCallback.length; i++) {
self.onRejectedCallback[i](reason)
}
}
})
}
try {
executor(resolve, reject)
} catch (reason) {
reject(reason)
}
}
function resolvePromise(promise2, x, resolve, reject) {
var then
var thenCalledOrThrow = false
if (promise2 === x) {
return reject(new TypeError('Chaining cycle detected for promise!'))
}
if (x instanceof Promise) {
if (x.status === 'pending') { //because x could resolved by a Promise Object
x.then(function(v) {
resolvePromise(promise2, v, resolve, reject)
}, reject)
} else { //but if it is resolved, it will never resolved by a Promise Object but a static value;
x.then(resolve, reject)
}
return
}
if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
try {
then = x.then //because x.then could be a getter
if (typeof then === 'function') {
then.call(x, function rs(y) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return resolvePromise(promise2, y, resolve, reject)
}, function rj(r) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return reject(r)
})
} else {
resolve(x)
}
} catch (e) {
if (thenCalledOrThrow) return
thenCalledOrThrow = true
return reject(e)
}
} else {
resolve(x)
}
}
Promise.prototype.then = function(onResolved, onRejected) {
var self = this
var promise2
onResolved = typeof onResolved === 'function' ? onResolved : function(v) {
return v
}
onRejected = typeof onRejected === 'function' ? onRejected : function(r) {
throw r
}
if (self.status === 'resolved') {
return promise2 = new Promise(function(resolve, reject) {
setTimeout(function() { // 異步執行onResolved
try {
var x = onResolved(self.data)
resolvePromise(promise2, x, resolve, reject)
} catch (reason) {
reject(reason)
}
})
})
}
if (self.status === 'rejected') {
return promise2 = new Promise(function(resolve, reject) {
setTimeout(function() { // 異步執行onRejected
try {
var x = onRejected(self.data)
resolvePromise(promise2, x, resolve, reject)
} catch (reason) {
reject(reason)
}
})
})
}
if (self.status === 'pending') {
// 這里之所以沒有異步執行,是因為這些函數必然會被resolve或reject調用,而resolve或reject函數里的內容已是異步執行,構造函數里的定義
return promise2 = new Promise(function(resolve, reject) {
self.onResolvedCallback.push(function(value) {
try {
var x = onResolved(value)
resolvePromise(promise2, x, resolve, reject)
} catch (r) {
reject(r)
}
})
self.onRejectedCallback.push(function(reason) {
try {
var x = onRejected(reason)
resolvePromise(promise2, x, resolve, reject)
} catch (r) {
reject(r)
}
})
})
}
}
Promise.prototype.catch = function(onRejected) {
return this.then(null, onRejected)
}
Promise.deferred = Promise.defer = function() {
var dfd = {}
dfd.promise = new Promise(function(resolve, reject) {
dfd.resolve = resolve
dfd.reject = reject
})
return dfd
}
四、promise的常用方法是如何實現
1、Promise.resolve / Promise.reject 實現
// 原生的Promise.resolve使用 Promise.resolve('hello swr').then((data)=>{ // 直接把成功的值傳遞給下一個then console.log(data) // hello swr }) // 那么Promise.resolve內部是怎么實現的呢? Promise.resolve = function(value){ return new Promise((resolve,reject)=>{ // 在內部new一個Promise對象 resolve(value) }) } // 同理,Promise.reject內部也是類似實現的 Promise.reject = function(reason){ return new Promise((resolve,reject)=>{ reject(reason) }) }
2、catch的實現
// 原生Promise的catch使用 Promise.reject('hello swr').catch((e)=>{ console.log(e) // hello swr }) // 上面這段代碼相當於下面這段代碼 Promise.reject('hello swr').then(null,(e)=>{ // then里直接走了失敗的回調 console.log(e) // hello swr }) // 內部實現 Promise.prototype.catch = function(onRejected){ return this.then(null,onRejected) // 相當於then里的成功回調只傳個null }
3、promise.all的實現同時執行多個異步,並且返回一個新的promise,成功的值是一個數組,該數組的成員的順序是傳參給promise.all的順序
// 原生Promise.all的使用 // 假設1.txt內容為hello 2.txt內容為swr let fs = require('fs') function read(filePath,encoding){ return new Promise((resolve,reject)=>{ fs.readFile(filePath,encoding,(err,data)=>{ if(err) reject(err) resolve(data) }) }) } Promise.all([read('./1.txt','utf8'),read('./2.txt','utf8')]).then((data)=>{ console.log(data) // 全部讀取成功后返回 ['hello','swr'] // 需要注意的是,當其中某個失敗的話,則會走失敗的回調函數 })
promise.all內部實現
Promise.all = function(promises){ // promises 是一個數組 return new Promise((resolve,reject)=>{ let arr = [] let i = 0 function processData(index,data){ arr[index] = data // 5.我們能用arr.length === promises.length來判斷請求是否全部完成嗎? // 答案是不行的,假設arr[2] = 'hello swr' // 那么打印這個arr,將是[empty × 2, "hello swr"], // 此時數組長度也是為3,而數組arr[0] arr[1]則為空 // 那么換成以下的辦法 if(++i === promises.length){ // 6.利用i自增來判斷是否都成功執行 resolve(arr) // 此時arr 為['hello','swr'] } } for(let i = 0;i < promises.length;i++){ // 1.在此處遍歷執行 promises[i].then((data)=>{ // 2.data是成功后返回的結果 processData(i,data) // 4.因為Promise.all最終返回的是一個數組成員按照順序排序的數組 // 而且異步執行,返回並不一定按照順序 // 所以需要傳當前的i },reject) // 3.如果其中有一個失敗的話,則調用reject } }) }
4、promise.race方法實現,同時執行多個異步,然后那個快,就用那個的結果,race是賽跑
// 原生Promise.race的使用 // 一個成功就走成功的回調,一個失敗就走失敗的回調 Promise.race([read('./1.txt','utf8'),read('./2.txt','utf8')]).then((data)=>{ console.log(data) // 可能返回 'hello' 也可能返回 'swr' 看哪個返回快就用哪個作為結果 }) // 內部實現 Promise.race = function(promises){ // promises 是一個數組 return new Promise((resolve,reject)=>{ for(let i = 0;i < promises.length;i++){ promises[i].then(resolve,reject) // 和上面Promise.all有點類似 } }) }
5、promise.defer = promise.deferred這個語法糖怎么理解呢?
這個語法糖可以簡化一些操作,比如:
let fs = require('fs') // 寫法一: function read(filePath,encoding){ // 這里的new Promise依然是傳遞了一個executor回調函數 // 我們該怎樣減少回調函數嵌套呢? return new Promise((resolve,reject)=>{ fs.readFile(filePath,encoding,(err,data)=>{ if(err) reject(err) resolve(data) }) }) } // 寫法二: // 這樣的寫法減少了一層回調函數的嵌套 function read(filePath,encoding){ let dfd = Promise.defer() fs.readFile(filePath,encoding,(err,data)=>{ if(err) dfd.reject(err) dfd.resolve(data) }) return dfd.promise } read('./1.txt','utf8').then((data)=>{ console.log(data) })
五、promise的鏈式調用
promise的核心在於:鏈式調用。
promise主要解決兩個問題:
(1)回調地獄
(2)並發的異步IO操作,同一時間內把這個結果拿到,比如有兩個異步io操作,當這2個獲取完畢后,才執行相應的代碼
1、回調地獄怎么解決
那么我們來看下面的代碼,並且改為promise。
// 回調函數 let fs = require('fs') fs.readFile('./a.txt','utf8',(err,data)=>{ // 往fs.readFile方法傳遞了第三個為函數的參數 if(err){ console.log(err) return } console.log(data) }) // 改寫為Promise let fs = require('fs') function read(filePath,encoding){ return new Promise((resolve,reject)=>{ fs.readFile(filePath,encoding,(err,data)=>{ if(err) reject(err) resolve() }) }) } read('./a.txt','utf8').then((data)=>{ // 在這里則不再需要傳回調函數進去,而是采用then來達到鏈式調用 console.log(data) },(err)=>{ console.log(err) })
這樣看好像Promise也沒什么優勢,那么接下來我們對比一下
// 假設有3個文件 // - 1.txt 文本內容為'2.txt' // - 2.txt 文本內容為'3.txt' // - 3.txt 文本內容為'hello swr' // 用回調函數 fs.readFile('./1.txt','utf8',(err,data)=>{ fs.readFile(data,'utf8',(err,data)=>{ fs.readFile(data,'utf8',(err,data)=>{ console.log(data) // hello swr }) }) }) // 用Promise read('./1.txt','utf8') .then((data)=>{ // 1.如果一個promise執行完后,返回的還是一個promise, // 會把這個promise的執行結果會傳遞給下一次then中 return read(data,'utf8') }) .then((data)=>{ return read(data,'utf8') }) .then((data)=>{ // 2.如果在then中返回的不是一個promise, // 而是一個普通值,會將這個普通值作為下次then的成功的結果 return data.split('').reverse().join('') }) .then((data)=>{ console.log(data) // rws olleh // 3.如果當前then中失敗了,會走下一個then的失敗回調 throw new Error('出錯') }) .then(null,(err)=>{ console.log(err) // Error:出錯 報錯了 // 4.如果在then中不返回值,雖然沒有顯式返回, // 但是默認是返回undefined,是屬於普通值,依然會把這個普通值傳到 // 下一個then的成功回調中 }) .then((data)=>{ console.log(data) // undefined })
從上面可以看得出,改寫為Promise的代碼,更好閱讀和維護,從用Promise方式可以得出結論:
(1)如果一個promise執行完后,返回的是一個promise,會將這個promise的執行結果傳遞給下一個then回調成功中;
(2)如果在then中返回的不是一個promise,而是一個普通的值,會將這個普通的值傳到下一個then成功回調中;
(3)如果當時then中失敗了,會走下一個then的回調失敗;
(4)如果then不返回值,但是默認是返回undefined的,屬於普通值,會將這個普通值傳到下一個then成功回調中。
如果在then中拋出錯誤,會怎么樣呢?
情況1:會被下一個then中的失敗回調捕獲
// 情景一,會被下一個then中的失敗回調捕獲 read('./1.txt','utf8') .then((data)=>{ throw new Error('出錯了') }) .then(null,(err)=>{ console.log(err) // Error:出錯了 報錯 })
情況2:沒有被失敗回調捕獲,拋出錯誤最終會變成異常
read('./1.txt','utf8') .then((data)=>{ throw new Error('出錯了') })
情況3:如果沒有被失敗的回調捕獲,那么最終會被catch捕獲到
read('./1.txt','utf8') .then((data)=>{ throw new Error('出錯了') }) .then((data)=>{ }) .catch((err)=>{ console.log(err) // Error:出錯了 報錯 })
情況4:如果被失敗的回調捕獲,那么就不會被catch捕獲到
read('./1.txt','utf8') .then((data)=>{ throw new Error('出錯了') }) .then(null,(err)=>{ console.log(err) // Error:出錯了 報錯 }) .catch((err)=>{ console.log(err) // 不會執行到這里 })
(5)catch是錯誤沒有處理的情況下才會執行
(6)then回調中可以不寫
六、關於promise的其他問題
1、性能問題
可能各位看官會覺得奇怪,Promise能有什么性能問題呢?並沒有大量的計算啊,幾乎都是處理邏輯的代碼。
理論上說,不能叫做“性能問題”,而只是有可能出現的延遲問題。什么意思呢,記得剛剛我們說需要把4塊代碼包在setTimeout里吧,先考慮如下代碼:
var start = +new Date() function foo() { setTimeout(function() { console.log('setTimeout') if((+new Date) - start < 1000) { foo() } }) } foo()
運行上面的代碼,會打印出多少次'setTimeout'呢,各位可以自己試一下,不出意外的話,應該是250次左右,我剛剛運行了一次,是241次。這說明,上述代碼中兩次setTimeout運行的時間間隔約是4ms(另外,setInterval也是一樣的),實事上,這正是瀏覽器兩次Event Loop之間的時間間隔,相關標准各位可以自行查閱。另外,在Node中,這個時間間隔跟瀏覽器不一樣,經過我的測試,是1ms。
單單一個4ms的延遲可能在一般的web應用中並不會有什么問題,但是考慮極端情況,我們有20個Promise鏈式調用,加上代碼運行的時間,那么這個鏈式調用的第一行代碼跟最后一行代碼的運行很可能會超過100ms,如果這之間沒有對UI有任何更新的話,雖然本質上沒有什么性能問題,但可能會造成一定的卡頓或者閃爍,雖然在web應用中這種情形並不常見,但是在Node應用中,確實是有可能出現這樣的case的,所以一個能夠應用於生產環境的實現有必要把這個延遲消除掉。在Node中,我們可以調用process.nextTick或者setImmediate(Q就是這么做的),在瀏覽器中具體如何做,已經超出了本文的討論范圍,總的來說,就是我們需要實現一個函數,行為跟setTimeout一樣,但它需要異步且盡早的調用所有已經加入隊列的函數,這里有一個實現。
2、如何停止一個promise鏈?
在一些場景里,我們會遇到一個較長的promise的鏈式調用,在某一步出現的錯誤讓我們沒有必要去運行鏈式調用后面所有的代碼,類似於下面這樣的(此處省略then/catch里的函數):
new Promise(function(resolve, reject) { resolve(42) }) .then(function(value) { // "Big ERROR!!!" }) .catch() .then() .then() .catch() .then()
假設這個`Big ERROR!!!`的出現讓我們完全沒有必要運行后面所有的代碼了,但鏈式調用的后面即有catch,也有then,無論我們是`return`還是`throw`,都不可避免的會進入某一個`catch`或`then`里面,那有沒有辦法讓這個鏈式調用在`Big ERROR!!!`的后面就停掉,完全不去執行鏈式調用后面所有回調函數呢?
從一個實現者的角度看問題時,確實找到了答案,就是在發生`Big ERROR`后return一個Promise,但這個Promise的executor函數什么也不做,這就意味着這個Promise將永遠處於`pending`狀態,由於then返回的Promise會直接取這個永遠處於`pending`狀態的Promise的狀態,於是返回的這個Promise也將一直處於`pending`狀態,后面的代碼也就一直不會執行了,具體代碼如下:
new Promise(function(resolve, reject) { resolve(42) }) .then(function(value) { // "Big ERROR!!!" return new Promise(function(){}) }) .catch() .then() .then() .catch() .then()
這種方式看起來有些山寨,它也確實解決了問題。但它引入的一個新問題就是鏈式調用后面的所有回調函數都無法被垃圾回收器回收(在一個靠譜的實現里,Promise應該在執行完所有回調后刪除對所有回調函數的引用以讓它們能被回收,在前文的實現里,為了減少復雜度,並沒有做這種處理),但如果我們不使用匿名函數,而是使用函數定義或者函數變量的話,在需要多次執行的Promise鏈中,這些函數也都只有一份在內存中,不被回收也是可以接受的。
將返回一個什么也不做的Promise封裝成一個有語義的函數,以增加代碼的可讀性
Promise.cancel = Promise.stop = function() { return new Promise(function(){}) }
這么使用了:
new Promise(function(resolve, reject) { resolve(42) }) .then(function(value) { // "Big ERROR!!!" return Promise.stop() }) .catch() .then() .then() .catch() .then()
3、promise的鏈上返回的最后一個promise出錯了怎么辦?
new Promise(function(resolve) { resolve(42) }) .then(function(value) { alter(value) })
但運行這段代碼的話你會發現什么現象也不會發生,既不會alert出42,也不會在控制台報錯,怎么回事呢。細看最后一行,`alert`被打成了`alter`,那為什么控制台也沒有報錯呢,因為`alter`所在的函數是被包在`try/catch`塊里的,`alter`這個變量找不到就直接拋錯了,這個錯就正好成了then返回的Promise的rejection reason。
解決辦法:
(1)所有的promise鏈的最后都加上一個catch,這樣出錯后就會被捕獲到,這樣違背了DRY原則,而且繁瑣;
(2)借鑒Q的一個方法done,把這個方法加到promise鏈的最后,就能夠處理捕獲最后一個promise出現的錯誤,其實個catch的思路一樣,這個是框架來實現的。
(3)在一個Promise被reject的時候檢查這個Promise的onRejectedCallback數組,如果它為空,則說明它的錯誤將沒有函數處理,這個時候,我們需要把錯誤輸出到控制台,讓開發者可以發現。
在Promise被reject但又沒有callback時,把錯誤輸出到控制台。
4、出錯時,使用throw new Error()還是使用return Promise.reject(new Error())呢?
從性能和編碼的舒適角度考慮:
(1)性能方面:throw new Error()會使代碼進入catch塊里的邏輯(我們把多有的回調都包在try/catch里),傳說throw多了會影響性能,因為一旦throw,代碼就有可能跳轉到不可預知的位置。
而使用promise.reject(new Error()),則需要構造一個新的promise對象(包含2個數組,4個函數:resolve/reject,onResolved/onRejected),也會花費一定的時間和內存。因為onResolved/onRejected函數是直接被包在promise實現里的try里,出錯后直接進入到這個try對應的catch塊,代碼的跳躍幅度相對較小,性能應該可以忽略不記。
(2)編碼的舒適度方面:出錯用throw,正常用return,正常可以明顯的區分出錯和正常
綜上覺得還是promise里發現顯式錯誤后,用throw拋出錯誤比較好,而不是顯式的構造一個唄reject的promise對象。
七、實踐
注意:
1、不要把promise寫成嵌套的結構
// 錯誤的寫法 promise1.then(function(value) { promise1.then(function(value) { promise1.then(function(value) { }) }) })
2、鏈式promise要返回一個promise,而不是構造一個promise
// 錯誤的寫法 Promise.resolve(1).then(function(){ Promise.resolve(2) }).then(function(){ Promise.resolve(3) })
八、參考
https://github.com/xieranmaya/blog/issues/3