錯誤處理

從 promises 丟擲的錯誤由傳遞給 then 的第二個引數(reject)或傳遞給 catch 的處理程式處理:

throwErrorAsync()
  .then(null, error => { /* handle error here */ });
// or
throwErrorAsync()
  .catch(error => { /* handle error here */ });

連結

如果你有一個 promise 鏈,那麼錯誤將導致跳過 resolve 處理程式:

throwErrorAsync()
  .then(() => { /* never called */ })
  .catch(error => { /* handle error here */ });

這同樣適用於你的 then 功能。如果 resolve 處理程式丟擲異常,則將呼叫下一個 reject 處理程式:

doSomethingAsync()
  .then(result => { throwErrorSync(); })
  .then(() => { /* never called */ })
  .catch(error => { /* handle error from throwErrorSync() */ });

錯誤處理程式返回一個新的 promise,允許你繼續一個 promise 鏈。錯誤處理程式返回的 promise 使用處理程式返回的值解析:

throwErrorAsync()
  .catch(error => { /* handle error here */; return result; })
  .then(result => { /* handle result here */ });

你可以通過重新丟擲錯誤讓一個錯誤級聯一個 promise 鏈:

throwErrorAsync()
  .catch(error => {
      /* handle error from throwErrorAsync() */
      throw error;
  })
  .then(() => { /* will not be called if there's an error */ })
  .catch(error => { /* will get called with the same error */ });

通過將 throw 語句包裝在 setTimeout 回撥中,可以丟擲未由 promise 承擔的異常:

new Promise((resolve, reject) => {
  setTimeout(() => { throw new Error(); });
});

這是有效的,因為 promises 不能處理非同步丟擲的異常。

未經處理的拒絕

如果 promise 沒有 catch 塊或 reject 處理程式,則會默默忽略錯誤:

throwErrorAsync()
  .then(() => { /* will not be called */ });
// error silently ignored

為防止這種情況,請始終使用 catch 塊:

throwErrorAsync()
  .then(() => { /* will not be called */ })
  .catch(error => { /* handle error*/ });
// or
throwErrorAsync()
  .then(() => { /* will not be called */ }, error => { /* handle error*/ });

或者,訂閱 unhandledrejection 事件以捕獲任何未處理的被拒絕的承諾:

window.addEventListener('unhandledrejection', event => {});

有些承諾可以在建立時間之後處理他們的拒絕。只要處理了這樣的承諾, rejectionhandled 事件就會被觸發:

window.addEventListener('unhandledrejection', event => console.log('unhandled'));
window.addEventListener('rejectionhandled', event => console.log('handled'));
var p = Promise.reject('test');

setTimeout(() => p.catch(console.log), 1000);

// Will print 'unhandled', and after one second 'test' and 'handled'

event 引數包含有關拒絕的資訊。event.reason 是錯誤物件,event.promise 是導致事件的 promise 物件。

在 Nodejs 中,rejectionhandledunhandledrejection 事件分別在 process 上被稱為 rejectionHandledunhandledRejection ,並且具有不同的簽名:

process.on('rejectionHandled', (reason, promise) => {});
process.on('unhandledRejection', (reason, promise) => {});

reason 引數是錯誤物件,promise 引數是對導致事件觸發的 promise 物件的引用。

應考慮使用這些 unhandledrejectionrejectionhandled 事件僅用於除錯目的。通常,所有承諾都應該處理他們的拒絕。

注意: 目前,只有 Chrome 49+和 Node.js 支援 unhandledrejectionrejectionhandled 活動。

注意事項

fulfillreject 連結

then(fulfill, reject) 函式(兩個引數都不是 null)具有獨特而複雜的行為,除非你確切知道它是如何工作的,否則不應該使用它。

如果為其中一個輸入提供 null,該函式將按預期工作:

// the following calls are equivalent
promise.then(fulfill, null) 
promise.then(fulfill)

// the following calls are also equivalent
promise.then(null, reject) 
promise.catch(reject)

但是,當給出兩個輸入時,它採用獨特的行為:

// the following calls are not equivalent!
promise.then(fulfill, reject)
promise.then(fulfill).catch(reject)

// the following calls are not equivalent!
promise.then(fulfill, reject)
promise.catch(reject).then(fulfill)

then(fulfill, reject) 函式看起來像是 then(fulfill).catch(reject) 的快捷方式,但它不是,如果可以互換使用會導致問題。一個這樣的問題是 reject 處理程式不處理來自 fulfill 處理程式的錯誤。以下是將要發生的事情:

Promise.resolve() // previous promise is fulfilled
    .then(() => { throw new Error(); }, // error in the fulfill handler
        error => { /* this is not called! */ });

上面的程式碼將導致拒絕的承諾,因為傳播了錯誤。將它與以下程式碼進行比較,從而產生一個滿足的承諾:

Promise.resolve() // previous promise is fulfilled
    .then(() => { throw new Error(); }) // error in the fulfill handler
    .catch(error => { /* handle error */ });

當使用 then(fulfill, reject)catch(reject).then(fulfill) 交替使用時,存在類似的問題,除了傳播履行的承諾而不是被拒絕的承諾。

從應該返回 promise 的函式同步丟擲

想象一下這樣的函式:

function foo(arg) {
  if (arg === 'unexepectedValue') {
    throw new Error('UnexpectedValue')
  }

  return new Promise(resolve => 
    setTimeout(() => resolve(arg), 1000)
  )
}

如果在 promise 鏈的中間使用了這樣的函式,那麼顯然沒有問題:

makeSomethingAsync().
  .then(() => foo('unexpectedValue'))
  .catch(err => console.log(err)) // <-- Error: UnexpectedValue will be caught here

但是,如果在 promise 鏈之外呼叫相同的函式,則錯誤將不會由它處理並將被丟擲到應用程式:

foo('unexpectedValue') // <-- error will be thrown, so the application will crash
  .then(makeSomethingAsync) // <-- will not run
  .catch(err => console.log(err)) // <-- will not catch

有兩種可能的解決方法:

返回帶有錯誤的被拒絕的承諾

而不是扔,做如下:

function foo(arg) {
  if (arg === 'unexepectedValue') {
    return Promise.reject(new Error('UnexpectedValue'))
  }

  return new Promise(resolve => 
    setTimeout(() => resolve(arg), 1000)
  )
}

將你的功能包裝到承諾鏈中

throw 語句已經在 promise 鏈中時,它將被正確捕獲:

function foo(arg) {
  return Promise.resolve()
    .then(() => {
      if (arg === 'unexepectedValue') {
        throw new Error('UnexpectedValue')
      }

      return new Promise(resolve => 
        setTimeout(() => resolve(arg), 1000)
      )
    })
}