La différence entre une fonction asynchrone et une fonction qui renvoie une promesse

Il existe une petite mais assez importante différence entre une fonction qui renvoie simplement une promesse et une fonction qui a été déclarée à l'aide du async .

Jetez un œil à l'extrait de code suivant:

 function fn(obj) { const someProp = obj.someProp return Promise.resolve(someProp) } async function asyncFn(obj) { const someProp = obj.someProp return Promise.resolve(someProp) } asyncFn().catch(err => console.error('Catched')) // => 'Catched' fn().catch(err => console.error('Catched')) // => TypeError: Cannot read property 'someProp' of undefined 

Comme vous pouvez le voir, les deux fonctions ont le même corps dans lequel nous essayons d'accéder à la propriété d'un argument qui n'est pas défini dans les deux cas. La seule différence entre les deux fonctions est que asyncFn déclaré à l'aide du async .

Cela signifie que JavaScript garantit que la fonction asnycFn renvoie une promesse (réussit ou échoue), même si une erreur s'y produit, dans notre cas, le bloc .catch() l'attrapera.

Cependant, dans le cas de la fonction fn , le moteur ne sait toujours pas que la fonction renverra une promesse, et donc le code n'atteindra pas le bloc .catch() , l'erreur ne sera pas interceptée et tombera sur la console.

Plus d'exemples de vie


Je sais ce que tu penses maintenant:
"Quand diable vais-je faire une telle erreur?"

Deviné?

Eh bien, créons une application simple qui fait exactement cela.

Supposons que nous ayons une application construite en utilisant Express et MongoDB qui utilise le pilote MongoDB Node.JS. Si vous ne me faites pas confiance, j'ai placé tout le code source dans ce référentiel Github , vous pouvez donc le cloner et l'exécuter localement, mais je dupliquerai également tout le code ici.

Voici notre fichier app.js :

 // app.js 'use strict' const express = require('express') const db = require('./db') const userModel = require('./models/user-model') const app = express() db.connect() app.get('/users/:id', (req, res) => { return userModel .getUserById(req.params.id) .then(user => res.json(user)) .catch(err => res.status(400).json({ error: 'An error occured' })) // <===  ! }) app.listen(3000, () => console.log('Server is listening')) 

Jetez un œil au bloc .catch() ! C'est là que la magie se produira (ne se produira pas).

Le fichier db.js utilisé pour se connecter à la base de données mongo:

 'use strict' const MongoClient = require('mongodb').MongoClient const url = 'mongodb://localhost:27017' const dbName = 'async-promise-test' const client = new MongoClient(url) let db module.exports = { connect() { return new Promise((resolve, reject) => { client.connect(err => { if (err) return reject(err) console.log('Connected successfully to server') db = client.db(dbName) resolve(db) }) }) }, getDb() { return db } } 

Et enfin, nous avons un fichier user-model.js dans lequel une getUserById fonction getUserById est actuellement définie:

 // models/user-model.js 'use strict' const ObjectId = require('mongodb').ObjectId const db = require('../db') const collectionName = 'users' module.exports = { /** * Get's a user by it's ID * @param {string} id The id of the user * @returns {Promise<Object>} The user object */ getUserById(id) { return db .getDb() .collection(collectionName) .findOne({ _id: new ObjectId(id) }) } } 

Si vous regardez à app.js fichier app.js , vous verrez que lorsque nous allons sur localhost:3000/users/<id> nous appelons la fonction getUserById définie dans le user-model.js , en passant le paramètre id comme demande.

Disons que vous allez à l'adresse suivante: localhost:3000/users/1 . Que pensez-vous qu'il va se passer ensuite?

Eh bien, si vous avez répondu: «Je verrai une énorme erreur de MongoClient» - vous aviez raison. Pour être plus précis, vous verrez l'erreur suivante: Error: Argument passed in must be a single String of 12 bytes or a string of 24 hex characters .

Et pensez-vous que le bloc .catch() sera appelé dans l'extrait de code suivant?

 // app.js // ...  ... app.get('/users/:id', (req, res) => { return userModel .getUserById(req.params.id) .then(user => res.json(user)) .catch(err => res.status(400).json({ error: 'An error occured' })) // <===  ! }) // ...  ... 

Non. Il ne sera pas appelé.

Et que se passe-t-il si vous changez une déclaration de fonction en ceci?

 module.exports = { //  ,    async    ! async findById(id) { return db .getDb() .collection(collectionName) .findOne({ _id: new ObjectId(id) }) } } 

Oui, vous commencez à comprendre ce qui est quoi. Notre bloc .catch() sera appelé et nous pourrons traiter l'erreur interceptée et la montrer à l'utilisateur.

Au lieu d'une conclusion


J'espère que ces informations ont été utiles à certains d'entre vous. Veuillez noter que cet article n'essaie pas de vous forcer à toujours utiliser des fonctions asynchrones - bien qu'elles soient plutôt cool. Ils ont leurs propres cas d'utilisation, mais ils sont toujours du sucre syntaxique par rapport aux promesses.

Je voulais juste que vous sachiez que parfois les promesses peuvent faire une grande différence, et quand (oui, pas «si») vous rencontrez l'erreur discutée dans cet article, vous saurez la raison possible de son occurrence.

Remarque PS perev.: à l'article d'origine, un commentaire utile a été laissé par l'utilisateur Craig P Hicks, que (après commentaires dans les commentaires) j'ai décidé de citer ici:
Je voudrais attirer l'attention sur un détail (dans mon environnement de développement), les erreurs qui se produisent dans le corps de Promise.resolve({<body>}) ne Promise.resolve({<body>}) pas "détectées":

 Promise.resolve((()=>{throw "oops"; })()) .catch(e=>console("Catched ",e)); //  .catch()      "" 

mais les erreurs qui se produisent dans le corps de la new Promise() ( traduction approximative: dans la «promesse appropriée» d'origine ) sont «détectées»:

 (new Promise((resolve,reject)=>{ resolve((()=>{throw "oops"})()) })) .catch(e=>console.log("Catched ",e)); // Catched oops 

Que diriez-vous de cette déclaration:

 async function fn() { <body> } 

sémantiquement, cette option est équivalente à ceci:

 function fn() { return new Promise((resolve,reject)=>{ resolve({ <body> }) }) } 

Par conséquent, le fragment de code ci-dessous détectera des erreurs si le <body> a une new Promise() ( environ. Trad.: Dans la «promesse appropriée» d'origine ):

 function fn() { return Promise.resolve({<body}); } 

Ainsi, pour que l'exemple du début de l'article "capture" les erreurs dans les deux cas, il est nécessaire de renvoyer non pas Promise.resolve() , mais de new Promise() dans les fonctions:

 function fn(obj) { return new Promise((resolve, reject) => { const someProp = obj.someProp; resolve(someProp); }); } async function asyncFn(obj) { return new Promise((resolve, reject) => { const someProp = obj.someProp; resolve(someProp); }); } asyncFn().catch(err => console.error("Catched")); // => 'Catched' fn().catch(err => console.error("Catched")); // => 'Catched' 

Source: https://habr.com/ru/post/fr475260/


All Articles