web-dev-qa-db-fra.com

Comment rejeter en syntaxe async/wait?

Comment puis-je rejeter une promesse renvoyée par une fonction async/wait?

par exemple. Initialement

foo(id: string): Promise<A> {
  return new Promise((resolve, reject) => {
    someAsyncPromise().then((value)=>resolve(200)).catch((err)=>reject(400))
  });
}

Traduire en asynchrone/wait

async foo(id: string): Promise<A> {
  try{
    await someAsyncPromise();
    return 200;
  } catch(error) {//here goes if someAsyncPromise() rejected}
    return 400; //this will result in a resolved promise.
  });
}

Alors, comment pourrais-je bien rejeter cette promesse dans ce cas?

148
Phoenix

Votre meilleur choix est de throw et une Error encapsulant la valeur, ce qui entraîne une promesse rejetée avec une Error encapsulant la valeur:

} catch (error) {
    throw new Error(400);
}

Vous pouvez également juste throw la valeur, mais alors il n'y a pas d'informations de trace de pile:

} catch (error) {
    throw 400;
}

Vous pouvez également renvoyer une promesse refusée avec une variable Error encapsulant la valeur:

} catch (error) {
    return Promise.reject(new Error(400));
}

(Ou simplement return Promise.reject(400);, mais là encore, il n'y a pas d'informations de contexte.)

(Dans votre cas, lorsque vous utilisez TypeScript et que foo a pour valeur Promise<A>, vous utiliseriez return Promise.reject<A>(400 /*or error*/);)

Dans une situation async/await, cette dernière est probablement un peu une mauvaise correspondance sémantique, mais cela fonctionne.

Si vous lancez une Error, cela fonctionne bien avec tout ce qui consomme le résultat de votre foo avec la syntaxe await:

try {
    await foo();
} catch (error) {
    // Here, `error` would be an `Error` (with stack trace, etc.).
    // Whereas if you used `throw 400`, it would just be `400`.
}
184
T.J. Crowder

Il convient également de mentionner que vous pouvez simplement chaîner une fonction catch() après l’appel de votre opération asynchrone, car une promesse est toujours retournée.

await foo().catch(error => console.log(error));

De cette façon, vous pouvez éviter la syntaxe try/catch si vous ne l'aimez pas.

96
David

Vous pouvez créer une fonction wrapper qui prend un promise et renvoie un tableau avec les données si aucune erreur et l'erreur s'il y a eu une erreur.

function safePromise(promise) {
  return promise.then(data => [ data ]).catch(error => [ null, error ]);
}

Utilisez-le comme ceci dans ES7 et dans une fonction async:

async function checkItem() {
  const [ item, error ] = await safePromise(getItem(id));
  if (error) { return null; } // handle error and return
  return item; // no error so safe to use item
}
8
Andy

Un meilleur moyen d'écrire la fonction asynchrone serait de renvoyer une promesse en attente depuis le début, puis de gérer les rejets et les résolutions dans le rappel de la promesse, plutôt que de simplement émettre une promesse rejetée en cas d'erreur. Exemple:

async foo(id: string): Promise<A> {
    return new Promise(function(resolve, reject) {
        // execute some code here
        if (success) { // let's say this is a boolean value from line above
            return resolve(success);
        } else {
            return reject(error); // this can be anything, preferably an Error object to catch the stacktrace from this function
        }
    });
}

Ensuite, il vous suffit d’enchaîner les méthodes sur la promesse retournée:

async function bar () {
    try {
        var result = await foo("someID")
        // use the result here
    } catch (error) {
        // handle error here
    }
}

bar()

Source - ce tutoriel: 

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

4
OzzyTheGiant

Ce n'est pas une réponse sur @ T.J. Celui de Crowder. Juste un commentaire répondant au commentaire "Et en fait, si l’exception doit être convertie en rejet, je ne suis pas sûr de savoir si cela me dérange si c’est une Erreur. Mes raisons de ne lancer que Erreur ne s’appliquent probablement pas. "

si votre code utilise async/await, il est néanmoins recommandé de rejeter avec une Error au lieu de 400:

try {
  await foo('a');
}
catch (e) {
  // you would still want `e` to be an `Error` instead of `400`
}
2
unional

Je sais que c’est une vieille question, mais je viens de trébucher sur le fil et il semble y avoir une confusion entre les erreurs et le rejet qui va à l’encontre (dans de nombreux cas au moins) du conseil souvent répété de ne pas utiliser la gestion des exceptions traiter les cas prévus. Pour illustrer ceci: si une méthode asynchrone tente d'authentifier un utilisateur et que l'authentification échoue, il s'agit d'un rejet (l'un des deux cas anticipés) et non d'une erreur (par exemple, si l'API d'authentification était indisponible.)

Pour m'assurer que je ne faisais pas que couper les cheveux en quatre, j'ai effectué un test de performance utilisant trois approches différentes: le code suivant:

const iterations = 100000;

function getSwitch() {
  return Math.round(Math.random()) === 1;
}

function doSomething(value) {
  return 'something done to ' + value.toString();
}

let processWithThrow = function () {
  if (getSwitch()) {
    throw new Error('foo');
  }
};

let processWithReturn = function () {
  if (getSwitch()) {
    return new Error('bar');
  } else {
    return {}
  }
};

let processWithCustomObject = function () {
  if (getSwitch()) {
    return {type: 'rejection', message: 'quux'};
  } else {
    return {type: 'usable response', value: 'fnord'};
  }
};

function testTryCatch(limit) {
  for (let i = 0; i < limit; i++) {
    try {
      processWithThrow();
    } catch (e) {
      const dummyValue = doSomething(e);
    }
  }
}

function testReturnError(limit) {
  for (let i = 0; i < limit; i++) {
    const returnValue = processWithReturn();
    if (returnValue instanceof Error) {
      const dummyValue = doSomething(returnValue);
    }
  }
}

function testCustomObject(limit) {
  for (let i = 0; i < limit; i++) {
    const returnValue = processWithCustomObject();
    if (returnValue.type === 'rejection') {
      const dummyValue = doSomething(returnValue);
    }
  }
}

let start, end;
start = new Date();
testTryCatch(iterations);
end = new Date();
const interval_1 = end - start;
start = new Date();
testReturnError(iterations);
end = new Date();
const interval_2 = end - start;
start = new Date();
testCustomObject(iterations);
end = new Date();
const interval_3 = end - start;

console.log(`with try/catch: ${interval_1}ms; with returned Error: ${interval_2}ms; with custom object: ${interval_3}ms`);

Certains des éléments qui y figurent ont été inclus en raison de mon incertitude quant à l’interprète Javascript (j’aime seulement descendre dans un terrier de lapin à la fois); Par exemple, j'ai inclus la fonction doSomething et assigné son retour à dummyValue pour que les blocs conditionnels ne soient pas optimisés.

Mes résultats ont été: 

with try/catch: 507ms; with returned Error: 260ms; with custom object: 5ms

Je sais qu'il y a beaucoup de cas où il ne vaut pas la peine de rechercher de petites optimisations, mais dans les systèmes à grande échelle, ces choses peuvent faire une grosse différence cumulative, et c'est une comparaison assez frappante.

SO… alors que je pense que la solution retenue est valable dans les cas où vous vous attendez à devoir gérer des erreurs imprévisibles dans une fonction asynchrone, dans les cas où un rejet signifie simplement «vous allez devoir utiliser Plan B (ou C ou D…) "Je pense que ma préférence serait de rejeter l’utilisation d’un objet de réponse personnalisé.

1
RiqueW

J'ai une suggestion pour correctement gérer les rejets dans une nouvelle approche, sans avoir plusieurs blocs try-catch.

import to from './to';

async foo(id: string): Promise<A> {
    let err, result;
    [err, result] = await to(someAsyncPromise()); // notice the to() here
    if (err) {
        return 400;
    }
    return 200;
}

Où la fonction to.ts doit être importée de:

export default function to(promise: Promise<any>): Promise<any> {
    return promise.then(data => {
        return [null, data];
    }).catch(err => [err]);
}

Les crédits vont à Dima Grossman dans le lien link .

0
Pedro Lourenço