为承诺链定义回退捕获?

时间:2016-09-27 15:08:54

标签: javascript promise ecmascript-6 es6-promise

我正在开发一个在整个地方使用what-wg fetch的应用程序。我们用这种方式定义了默认的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('/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之后链接

有关如何使用whatwg-fetch / ES6 Promises实现这一目标的任何提示?

相关:

有类似的帖子,但它们似乎没有解决在所有非默认thencatch之后运行的默认捕获的需要:

编辑10月14日:

可能重复:Promises and generic .catch() statements

2 个答案:

答案 0 :(得分:2)

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

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

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

达到此目的的最简单方法是为fetch承诺引入类似承诺的包装:

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 */);
});

一个很好的选择是蓝鸟,不需要在那里发明轮子,功能是它所喜爱的。 Local rejection events看起来正是所需要的:

// 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));
}
P,这很容易。

答案 1 :(得分:0)

我认为解决方案很简单:

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
});