JavaScript La gestion des erreurs


Exemple

Les erreurs générées par les promesses sont gérées par le second paramètre ( reject ) passé à then ou par le gestionnaire transmis à catch :

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

Chaînage

Si vous avez une chaîne de promesses, une erreur provoquera l' resolve gestionnaires de resolve :

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

La même chose s'applique à vos fonctions then . Si un gestionnaire de resolve renvoie une exception, le gestionnaire de reject suivant sera appelé:

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

Un gestionnaire d'erreur renvoie une nouvelle promesse, vous permettant de continuer une chaîne de promesses. La promesse renvoyée par le gestionnaire d'erreurs est résolue avec la valeur renvoyée par le gestionnaire:

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

Vous pouvez laisser une erreur tomber en cascade sur une chaîne de promesses en relançant l'erreur:

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

Il est possible de lancer une exception qui n'est pas gérée par la promesse en encapsulant l'instruction throw dans un rappel setTimeout :

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

Cela fonctionne car les promesses ne peuvent pas gérer les exceptions lancées de manière asynchrone.

Rejets non gérés

Une erreur sera ignorée en silence si une promesse n'a pas de bloc catch ou de gestionnaire de reject :

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

Pour éviter cela, utilisez toujours un bloc catch :

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

Autrement, abonnez-vous à l'événement unhandledrejection pour détecter toute promesse rejetée non gérée:

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

Certaines promesses peuvent gérer leur rejet après leur création. L'événement de rejectionhandled est déclenché chaque fois qu'une telle promesse est gérée:

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'

L'argument d' event contient des informations sur le rejet. event.reason est l'objet d'erreur et event.promise est l'objet prometteur ayant provoqué l'événement.

En NodeJS les rejectionhandled et unhandledrejection événements sont appelés rejectionHandled et unhandledRejection sur process , respectivement, et ont une signature différente:

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

L'argument reason est l'objet erreur et l'argument promise une référence à l'objet prometteur qui a déclenché l'événement.

L'utilisation de ces événements unhandledrejection et rejectionhandled doit être considérée uniquement à des fins de débogage. En règle générale, toutes les promesses doivent gérer leurs rejets.

Note: À l' heure actuelle, seul le soutien Chrome 49+ et Node.js unhandledrejection et rejectionhandled événements.

Mises en garde

Enchaînement avec fulfill et reject

La fonction then(fulfill, reject) (avec les deux paramètres non null ) a un comportement unique et complexe, et ne devrait pas être utilisé à moins de savoir exactement comment cela fonctionne.

La fonction fonctionne comme prévu si on lui donne la valeur null pour l'une des entrées:

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

Toutefois, il adopte un comportement unique lorsque les deux entrées sont données:

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

La fonction then(fulfill, reject) ressemble à un raccourci pour then(fulfill).catch(reject) , mais ce n’est pas le cas et cela posera des problèmes si elle est utilisée de manière interchangeable. Un tel problème est que le reject gestionnaire ne gère pas les erreurs de la fulfill gestionnaire. Voici ce qui va arriver:

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

Le code ci-dessus entraînera une promesse rejetée parce que l'erreur est propagée. Comparez-le au code suivant, ce qui aboutit à une promesse remplie:

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

Un problème similaire existe quand on utilise then(fulfill, reject) indifféremment avec catch(reject).then(fulfill) , sauf avec la propagation de promesses remplies au lieu de promesses rejetées.

Lancer de manière synchrone à partir d'une fonction qui devrait renvoyer une promesse

Imaginez une fonction comme celle-ci:

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

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

Si une telle fonction est utilisée au milieu d'une chaîne de promesses, alors apparemment, il n'y a pas de problème:

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

Cependant, si la même fonction est appelée en dehors d’une chaîne de promesses, l’erreur ne sera pas gérée et sera envoyée à l’application:

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

Il y a 2 solutions possibles:

Renvoyer une promesse rejetée avec l'erreur

Au lieu de lancer, procédez comme suit:

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

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

Enveloppez votre fonction dans une chaîne de promesses

Votre déclaration de throw sera correctement capturée lorsqu'elle se trouve déjà dans une chaîne de promesses:

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

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