为承诺链定义回退捕获

Define fallback catch for promise chain?

本文关键字:回退 定义 承诺      更新时间:2023-09-26

我正在开发一个到处使用what-wg fetch的应用程序。我们已经这样定义了默认的获取中间件和选项:

export function fetchMiddleware(response) {
  return new Promise(resolve => {
    resolve(checkStatus(response));
  }).then(parseJSON);
}
export const fetchDefaults = {
  credentials: 'same-origin',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json'
  }
};

我们这样使用默认的中间件/fetch选项:

fetch('/api/specific/route', fetchDefaults)
  .then(fetchMiddleware)
  .then(function(data) {
    // ... Dispatch case-specific fetch outcome
    dispatch(specificRouteResponseReceived(data));
  });

我们希望在整个应用程序中为所有fetch用法添加一个通用的回退捕获,换句话说,就像这样:

export function fetchGenericCatch(function(error) {
  showGenericErrorFlashMessage();
})
fetch('/en/api/user/me/preferences', fetchDefaults)
  .then(fetchMiddleware)
  .then(function(data) {
    dispatch(userPreferencesReceived(data));
  })
  .catch(fetchGenericCatch);

大量的代码重复。我们想要一个实用函数/类,它可以为我们做所有这些,例如,像这样的东西:

genericFetch('/api/specific/route') // bakes in fetchDefaults and fetchMiddleware and fetchGenericCatch
  .then(function(data) {
    dispatch(userPreferencesReceived(data));
  }); // gets generic failure handler for free
genericFetch('/api/specific/route') // bakes in fetchDefaults and fetchMiddleware and fetchGenericCatch
  .then(function(data) {
    dispatch(userPreferencesReceived(data));
  })
  .catch(function(error) {
    // ...
  }); // short-circuits generic error handler with case-specific error handler

主要的警告是,通用catch必须链接在特定情况的then s/catch es之后。

关于如何使用whatwg-fetch/ES6 Promises实现这一点,有什么建议吗?

相关:

有类似的帖子,但他们似乎没有解决在所有非默认then s和catch es之后运行的默认catch的需要:

  • 如何创建带有默认catch和处理程序的Promise
  • 当没有其他函数链接到承诺时的默认行为

编辑14 Oct:

可能重复:Promises和泛型.catch()语句

使用WET代码并不是最糟糕的选择,只要错误处理程序是DRY。

fetch(...)
...
.catch(importedFetchHandler);

它不会引起任何问题,并且符合Bluebird和V8承诺的行为,其中存在未处理的拒绝事件以确保没有未捕获的承诺。


最简单的方法是为fetch promise引入类似promise的包装器:

function CatchyPromiseLike(originalPromise) {
  this._promise = originalPromise;
  this._catchyPromise = Promise.resolve()
  .then(() => this._promise)
  .catch((err) => {
    console.error('caught', err);
  });
  // every method but 'constructor' from Promise.prototype 
  const methods = ['then', 'catch'];
  for (const method of methods) {
    this[method] = function (...args) {
      this._promise = this._promise[method](...args);
      return this;
    }
  }
}

可以像

一样使用
function catchyFetch(...args) {
  return new CatchyPromiseLike(fetch(...args));
}

一个有自然限制的承诺。

如果转换为真正的承诺,副作用将被丢弃:

Promise.resolve(catchyFetch(...)).then(() => /* won't be caught */);

它不能很好地与异步链(这是所有承诺的禁忌):

var promise = catchyFetch(...);
setTimeout(() => {
  promise.then(() => /* won't be caught */);
});

一个很好的选择是Bluebird,不需要在那里发明轮子,功能是它所喜欢的。本地拒绝事件看起来正是我们所需要的:

// An important part here,
// only the promises used by catchyFetch should be affected
const CatchyPromise = Bluebird.getNewLibraryCopy();
CatchyPromise.onPossiblyUnhandledRejection((err) => {
  console.error('caught', err);
});
function catchyFetch(...args) {
  return CatchyPromise.resolve(fetch(...args));
}

我认为解决办法很简单:

export function genericFetch(url, promise, optionOverrides) {
  const fetchOptions = {...fetchDefaults, ...optionOverrides};
  return fetch(url, fetchOptions)
    .then(fetchMiddleware)
    .then(promise)
    .catch(function(error) {
      showGenericFlashMessage();
    });
}

不需要特殊错误处理程序的用例可以简单地这样使用:

genericFetch('/api/url', function(data) {
  dispatch(apiResponseReceived(data));
});

需要特殊捕获或更复杂链的用例可以传入一个完整的承诺:

genericFetch('/api/url', function(response) {
  return new Promise(resolve, reject => {
    dispatch(apiResponseReceived(data));
  }).catch(nonGenericCaseSpecificCatch); // short-circuits default catch
});