web-dev-qa-db-fra.com

Les promesses ne sont-elles pas simplement des rappels?

Je développe JavaScript depuis quelques années et je ne comprends pas du tout la question des promesses.

Il semble que tout ce que je fais est de changer:

api(function(result){
    api2(function(result2){
        api3(function(result3){
             // do work
        });
    });
});

Ce que je pourrais utiliser une bibliothèque comme asynchrone pour toujours, avec quelque chose comme:

api().then(function(result){
     api2().then(function(result2){
          api3().then(function(result3){
               // do work
          });
     });
});

Ce qui est plus de code et moins lisible. Je n'ai rien gagné ici, ce n'est pas soudainement "plat" non plus. Sans parler de devoir convertir les choses en promesses.

Alors, quel est le gros problème avec les promesses ici?

391
Benjamin Gruenbaum

Les promesses ne sont pas des rappels. Une promesse représente le résultat futur d'une opération asynchrone. Bien sûr, en les écrivant comme vous le faites, vous n’obtenez que peu d’avantages. Mais si vous les écrivez comme ils sont censés être utilisés, vous pouvez écrire du code asynchrone d’une manière qui ressemble au code synchrone et qui est beaucoup plus facile à suivre:

api().then(function(result){
    return api2();
}).then(function(result2){
    return api3();
}).then(function(result3){
     // do work
});

Certainement, pas beaucoup moins de code, mais beaucoup plus lisible.

Mais ce n'est pas la fin. Découvrons les véritables avantages: et si vous vouliez vérifier s'il y a une erreur dans l'une des étapes? Ce serait un enfer de le faire avec des rappels, mais avec des promesses, c'est un jeu d'enfant:

api().then(function(result){
    return api2();
}).then(function(result2){
    return api3();
}).then(function(result3){
     // do work
}).catch(function(error) {
     //handle any error that may occur before this point
});

Quasiment la même chose qu’un bloc try { ... } catch.

Encore mieux:

api().then(function(result){
    return api2();
}).then(function(result2){
    return api3();
}).then(function(result3){
     // do work
}).catch(function(error) {
     //handle any error that may occur before this point
}).then(function() {
     //do something whether there was an error or not
     //like hiding an spinner if you were performing an AJAX request.
});

Et encore mieux: que faire si ces 3 appels à api, api2, api3 pouvaient être exécutés simultanément (par exemple, s’il s’agissait d'appels AJAX) mais que vous deviez attendre les trois ? Sans promesses, vous devriez créer une sorte de compteur. Avec des promesses, en utilisant la notation ES6, est un autre morceau de gâteau et assez soigné:

Promise.all([api(), api2(), api3()]).then(function(result) {
    //do work. result is an array contains the values of the three fulfilled promises.
}).catch(function(error) {
    //handle the error. At least one of the promises rejected.
});

J'espère que vous voyez les promesses sous un jour nouveau.

582
Oscar Paz

Oui, les promesses sont des rappels asynchrones. Ils ne peuvent rien faire que les rappels ne puissent pas, et vous rencontrez les mêmes problèmes d'asynchronisme qu'avec les rappels en clair.

Cependant, les promesses sont plus que de simples rappels. Ils constituent une abstraction très puissante, permettent un code fonctionnel plus propre et de meilleure qualité avec un passe-partout moins sujet aux erreurs.

Alors, quelle est l'idée principale?

Les promesses sont des objets représentant le résultat d'un calcul unique (asynchrone). Ils résolvent à ce résultat une seule fois. Voici ce que cela signifie:

Les promesses implémentent un modèle d'observateur:

  • Vous n'avez pas besoin de connaître les rappels qui utiliseront la valeur avant la fin de la tâche.
  • Au lieu d'attendre des callbacks comme arguments de vos fonctions, vous pouvez facilement return un objet Promise
  • La promesse va stocker la valeur, et vous pouvez de manière transparente ajouter un rappel à tout moment. Il sera appelé lorsque le résultat sera disponible. La "transparence" implique que lorsque vous avez une promesse et que vous y ajoutez un rappel, le résultat n'est pas encore différent pour votre code. L'API et les contrats sont identiques, ce qui simplifie beaucoup la mise en cache/mémorisation.
  • Vous pouvez ajouter plusieurs rappels facilement

Les promesses sont chaînables ( monadique , si vous voulez ):

  • Si vous devez transformer la valeur représentée par une promesse, vous mappez une fonction de transformation sur la promesse et récupérez une nouvelle promesse qui représente le résultat transformé. Vous ne pouvez pas obtenir de manière synchrone la valeur pour l'utiliser, mais vous pouvez facilement lever la transformation dans le contexte de la promesse. Aucun rappel de type passe-partout.
  • Si vous souhaitez chaîner deux tâches asynchrones, vous pouvez utiliser la méthode .then(). Il faudra un rappel pour être appelé avec le premier résultat et renvoie une promesse pour le résultat de la promesse que le rappel est retourné.

Cela semble compliqué? Temps pour un exemple de code.

var p1 = api1(); // returning a promise
var p3 = p1.then(function(api1Result) {
    var p2 = api2(); // returning a promise
    return p2; // The result of p2 …
}); // … becomes the result of p3

// So it does not make a difference whether you write
api1().then(function(api1Result) {
    return api2().then(console.log)
})
// or the flattened version
api1().then(function(api1Result) {
    return api2();
}).then(console.log)

L'aplatissement ne vient pas comme par magie, mais vous pouvez le faire facilement. Pour votre exemple fortement imbriqué, l'équivalent (proche) serait

api1().then(api2).then(api3).then(/* do-work-callback */);

Si voir le code de ces méthodes aide à comprendre, voici une librairie de promesse la plus élémentaire en quelques lignes .

Quel est le problème avec les promesses?

L'abstraction de Promise permet une bien meilleure composition des fonctions. Par exemple, à côté de then pour le chaînage, la fonction all crée une promesse pour le résultat combiné de plusieurs promesses d'attente parallèle.

Dernier point, mais non des moindres, les promesses s'accompagnent d'une gestion intégrée des erreurs. Le résultat du calcul peut être que soit la promesse est remplie avec une valeur, soit elle est rejetée avec une raison. Toutes les fonctions de composition gèrent cela automatiquement et propagent les erreurs dans les chaînes de promesses, de sorte que vous n'ayez pas à vous en soucier explicitement partout - contrairement à une implémentation en clair. En fin de compte, vous pouvez ajouter un rappel d'erreur dédié pour toutes les exceptions survenues.

Sans parler de devoir convertir les choses en promesses.

C'est assez trivial avec les bibliothèques de bonnes promesses, voir Comment convertir une API de rappel existante en promesses?

157
Bergi

En plus des réponses déjà établies, avec les fonctions fléchées ES6, les promesses tournent d’un petit nain bleu modestement brillant droit à un géant rouge. C'est sur le point de s'effondrer dans une supernova:

api().then(result => api2()).then(result2 => api3()).then(result3 => console.log(result3))

Comme oligofren a souligné, sans arguments entre les appels d'API, vous n'avez pas du tout besoin des fonctions d'encapsidation anonymes:

api().then(api2).then(api3).then(r3 => console.log(r3))

Et enfin, si vous voulez atteindre un niveau de trou noir supermassif, des promesses peuvent être attendues:

async function callApis() {
    let api1Result = await api();
    let api2Result = await api2(api1Result);
    let api3Result = await api3(api2Result);

    return api3Result;
}
18
John Weisz

Outre les autres réponses, la syntaxe ES2015 se mêle parfaitement aux promesses, réduisant encore le code standard:

// Sequentially:
api1()
  .then(r1 => api2(r1))
  .then(r2 => api3(r2))
  .then(r3 => {
      // Done
  });

// Parallel:
Promise.all([
    api1(),
    api2(),
    api3()
]).then(([r1, r2, r3]) => {
    // Done
});
11
Duncan Luk

En plus des réponses impressionnantes ci-dessus, 2 points supplémentaires peuvent être ajoutés:

1. Différence sémantique:

Les promesses peuvent être déjà résolues lors de la création. Cela signifie qu'ils garantissent des conditions plutôt que des événements . S'ils sont déjà résolus, la fonction résolue qui lui est transmise est toujours appelée.

Inversement, les rappels gèrent les événements. Ainsi, si l'événement qui vous intéresse a eu lieu avant l'enregistrement du rappel, le rappel n'est pas appelé.

2. Inversion de contrôle

Les rappels impliquent une inversion de contrôle. Lorsque vous enregistrez une fonction de rappel avec une API, le moteur d'exécution Javascript enregistre la fonction de rappel et l'appelle à partir de la boucle d'événements une fois qu'elle est prête à être exécutée.

Reportez-vous à la boucle d'événement Javascript pour une explication.

Avec Promises , le contrôle appartient au programme appelant. La méthode .then () peut être appelée à tout moment si nous stockons l'objet promis.

9
dww

Les promesses ne sont pas des rappels, ce sont toutes deux des idiomes de programmation qui facilitent la programmation asynchrone. Utiliser une programmation de type asynchrone ou en attente utilisant des routines ou des générateurs qui renvoient des promesses pourrait être considéré comme un troisième idiome de ce type. Une comparaison de ces expressions entre différents langages de programmation (y compris le langage Javascript) est disponible ici: https://github.com/KjellSchubert/promise-future-task

4
Kjell Schubert

Aucune promesse ne fait qu'emballer les rappels

exemple Vous pouvez utiliser des promesses natives javascript avec le noeud js

my cloud 9 code link : https://ide.c9.io/adx2803/native-promises-in-node

/**
* Created by dixit-lab on 20/6/16.
*/

var express = require('express');
var request = require('request');   //Simplified HTTP request client.


var app = express();

function promisify(url) {
    return new Promise(function (resolve, reject) {
    request.get(url, function (error, response, body) {
    if (!error && response.statusCode == 200) {
        resolve(body);
    }
    else {
        reject(error);
    }
    })
    });
}

//get all the albums of a user who have posted post 100
app.get('/listAlbums', function (req, res) {
//get the post with post id 100
promisify('http://jsonplaceholder.typicode.com/posts/100').then(function (result) {
var obj = JSON.parse(result);
return promisify('http://jsonplaceholder.typicode.com/users/' + obj.userId + '/albums')
})
.catch(function (e) {
    console.log(e);
})
.then(function (result) {
    res.end(result);
}
)

})


var server = app.listen(8081, function () {

var Host = server.address().address
var port = server.address().port

console.log("Example app listening at http://%s:%s", Host, port)

})


//run webservice on browser : http://localhost:8081/listAlbums
1
Apoorv

Non pas du tout.

Callbacks sont simplement des fonctions dans JavaScript qui doivent être appelées puis exécutées à la fin de l'exécution d'une autre fonction. Alors comment ça se passe?

En réalité, en JavaScript, les fonctions sont elles-mêmes considérées comme des objets et, comme tous les autres objets, même des fonctions peuvent être envoyées comme arguments à other fonctions . Le cas d'utilisation le plus courant et le plus générique auquel on puisse penser est la fonction setTimeout () en JavaScript.

Promises n’est rien de moins qu’une approche beaucoup plus improvisée de la manipulation et de la structuration du code asynchrone par rapport à la même chose avec les rappels.

La promesse reçoit deux rappels dans la fonction constructeur: résoudre et rejeter. Ces rappels à l'intérieur des promesses nous fournissent un contrôle fin de la gestion des erreurs et des cas de réussite. Le rappel de résolution est utilisé lorsque l'exécution de la promesse est exécutée avec succès et le rappel de rejet est utilisé pour gérer les cas d'erreur.

0
Ayush Jain