简体   繁体   中英

Define fallback catch for promise chain?

I'm working on an application that uses what-wg fetch all over the place. We've defined default fetch middleware and options this way:

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

We use our default middleware / fetch options this way:

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

We want to add a generic, fallback catch to all fetch usages throughout the application, in other words something like this:

export function fetchGenericCatch(function(error) {
  showGenericErrorFlashMessage();
})

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

Lots of code duplication. We'd like a utility function / class which can do all of this for us, eg something which would work like this:

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

The main caveat is that the generic catch must be chained after the case-specific then s / catch es.

Any tips on how this might be achieved using whatwg-fetch / ES6 Promises?

Related:

There are similar posts, but they don't seem to address the need for a default catch which runs after all non-default then s and catch es:

Edit 14 Oct:

Possible duplicate: Promises and generic .catch() statements

Having WET code isn't the worst option here, as long as error handler is DRY.

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

It causes no problems and conforms to the behaviour of Bluebird and V8 promises, where unhandled rejection event exists to make sure that no promise are left uncaught.


The most simple way to reach this is to introduce promise-like wrapper for fetch 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;
    }
  }
}

which can be used like

function catchyFetch(...args) {
  return new CatchyPromiseLike(fetch(...args));
}

A promise-like like that has natural limitations.

Side effects are discarded if converted to real promise:

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

And it won't play well with asynchronous chain (this is a no-no for all promises):

var promise = catchyFetch(...);

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

A good alternative is Bluebird, no need to invent the wheel there, features is what it is loved for. Local rejection events look like exactly what's needed:

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

Phew, that was easy.

I think the solution is as simple as:

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

A use case which doesn't need a special error handler can simply use it this way:

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

A use case which needs a special catch, or a more complex chain, can pass in a full-blown promise:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM