Le promesse non sono solo richiamate?


430

Sto sviluppando JavaScript da alcuni anni e non capisco affatto il clamore delle promesse.

Sembra che tutto ciò che faccio sia cambiare:

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

Che potrei usare comunque una libreria come asincrona , con qualcosa del tipo:

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

Che è più codice e meno leggibile. Non ho guadagnato nulla qui, non è nemmeno improvvisamente magicamente "piatto". Per non parlare del fatto di dover convertire le cose in promesse.

Allora, qual è il grande clamore delle promesse qui?


11
A proposito: c'è un articolo davvero informativo sulle promesse su Html5Rocks: html5rocks.com/en/tutorials/es6/promises
ComFreek

2
Fyi la risposta che hai accettato è la stessa vecchia lista dei banali benefici che non sono affatto il punto delle promesse e non mi hanno nemmeno convinto a usare le promesse: /. Ciò che mi ha convinto a usare le promesse è stato l'aspetto DSL come descritto nella risposta di Oscar
Esailija

@Esailija bene, il tuo leet parlare mi ha convinto. Ho accettato l'altra risposta, anche se penso che quella di Bergi sollevi anche dei punti davvero buoni (e diversi).
Benjamin Gruenbaum,

@Esailija "Ciò che mi ha convinto a usare le promesse è stato l'aspetto DSL come descritto nella risposta di Oscar" << Che cos'è "DSL"? e qual è l '"aspetto DSL" a cui ti riferisci?
monsto

1
@monsto: DSL: Domain Specific Language, un linguaggio appositamente progettato per essere utilizzato in un particolare sottoinsieme di un sistema (ad esempio SQL o ORM per parlare con il database, regex per trovare schemi, ecc.). In questo contesto il "DSL" è l'API della Promessa che, se strutturi il tuo codice come ha fatto Oscar, è quasi come lo zucchero sintattico che integra JavaScript per affrontare il particolare contesto delle operazioni asincrone. Le promesse creano alcuni modi di dire che li trasformano in quasi un linguaggio progettato per consentire al programmatore di cogliere più facilmente il flusso mentale un po 'sfuggente di questo tipo di strutture.
Michael Ekoka,

Risposte:


631

Le promesse non sono callback. Una promessa rappresenta il risultato futuro di un'operazione asincrona . Naturalmente, scrivendoli come fai tu, ottieni pochi benefici. Ma se li scrivi nel modo in cui devono essere utilizzati, puoi scrivere codice asincrono in modo simile al codice sincrono ed è molto più facile da seguire:

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

Certamente, non molto meno codice, ma molto più leggibile.

Ma questa non è la fine. Scopriamo i veri vantaggi: cosa succede se si desidera verificare la presenza di errori in uno dei passaggi? Sarebbe un inferno farlo con i callback, ma con le promesse, è un gioco da ragazzi:

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

Praticamente uguale a un try { ... } catchblocco.

Anche meglio:

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

E ancora meglio: cosa succede se quei 3 chiamate a api, api2, api3potrebbero funzionare simultaneamente (ad esempio, se fossero chiamate AJAX), ma è necessario attendere che il tre? Senza promesse, dovresti creare una sorta di contatore. Con le promesse, usando la notazione ES6, è un altro pezzo di torta e piuttosto pulito:

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

Spero che tu veda Promesse in una nuova luce ora.


124
Non avrebbero dovuto chiamarlo "Promessa". Il "futuro" è almeno 100 volte migliore.
Pacerier,

12
@Pacerier perché Future non è stato contaminato da jQuery?
Esailija,

5
Pattern alternativo (a seconda di ciò che si desidera: api (). Then (api2) .then (api3) .then (doWork); Cioè, se le funzioni api2 / api3 prendono input dall'ultimo passo e restituiscono nuove promesse, esse possono essere semplicemente incatenati senza ulteriore avvolgimento. Cioè, compongono
Dtipson il

1
Cosa succede se ci sono operazioni asincrone in api2e api3? l'ultimo .thensarebbe chiamato solo una volta completate quelle operazioni asincrone?
NiCk Newman,

8
Perché mi hai taggato? Ho appena risolto un po 'la grammatica. Non sono un esperto di JS. :)
Scott Arciszewski,

169

Sì, le promesse sono callback asincroni. Non possono fare nulla che i callback non possano fare e tu incontri gli stessi problemi con l'asincronia dei callback normali.

Tuttavia, le promesse sono più che semplici callback. Sono un'astrazione molto potente, consentono un codice più pulito e migliore e funzionale con una caldaia meno soggetta a errori.

Qual è l'idea principale?

Le promesse sono oggetti che rappresentano il risultato di un singolo calcolo (asincrono). Essi risolvono a tale risultato solo una volta. Ci sono alcune cose che cosa significa:

Le promesse implementano un modello di osservatore:

  • Non è necessario conoscere i callback che utilizzeranno il valore prima che l'attività venga completata.
  • Invece di aspettarti callback come argomenti per le tue funzioni, puoi facilmente returnun oggetto Promise
  • La promessa memorizzerà il valore e potrai aggiungere in modo trasparente una richiamata ogni volta che lo desideri. Verrà chiamato quando il risultato è disponibile. "Trasparenza" implica che quando si ha una promessa e si aggiunge un callback, non fa alcuna differenza al codice se il risultato è ancora arrivato - l'API e i contratti sono gli stessi, semplificando molto la memorizzazione nella cache / memorizzazione.
  • È possibile aggiungere più callback facilmente

Le promesse sono concatenabili ( monadiche , se vuoi ):

  • Se è necessario trasformare il valore rappresentato da una promessa, mappare una funzione di trasformazione sulla promessa e ottenere una nuova promessa che rappresenta il risultato trasformato. Non è possibile ottenere in modo sincrono il valore per usarlo in qualche modo, ma è possibile sollevare facilmente la trasformazione nel contesto della promessa. Nessun callback del plateplate.
  • Se si desidera concatenare due attività asincrone, è possibile utilizzare il .then()metodo Ci vorrà un callback per essere chiamato con il primo risultato e restituisce una promessa per il risultato della promessa che il callback ritorna.

Sembra complicato? Tempo per un esempio di codice.

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'appiattimento non arriva magicamente, ma puoi farlo facilmente. Per il tuo esempio fortemente annidato, l'equivalente (quasi) sarebbe

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

Se vedere il codice di questi metodi aiuta a capire, ecco una promessa di base in poche righe .

Qual è il grande clamore delle promesse?

L'astrazione Promise consente una migliore componibilità delle funzioni. Ad esempio, accanto al thenconcatenamento, la allfunzione crea una promessa per il risultato combinato di più promesse di attesa parallele.

Ultimo ma non meno importante, le promesse vengono fornite con la gestione integrata degli errori. Il risultato del calcolo potrebbe essere che o la promessa è adempiuta con un valore o è respinta con una ragione. Tutte le funzioni di composizione gestiscono questo automaticamente e propagano gli errori nelle catene di promesse, quindi non è necessario preoccuparsene esplicitamente dappertutto, a differenza di un'implementazione di semplice callback. Alla fine, è possibile aggiungere un callback di errore dedicato per tutte le eccezioni verificatesi.

Per non parlare del fatto di dover convertire le cose in promesse.

È piuttosto banale in realtà con buone librerie di promesse, vedi Come posso convertire un'API di callback esistente in promesse?


ciao Bergi, avresti qualcosa di interessante da aggiungere a questa domanda SO? stackoverflow.com/questions/22724883/…
Sebastien Lorber

1
@Sebastien: Non so molto di Scala (ancora), e potrei solo ripetere ciò che Benjamin ha detto :-)
Bergi

3
Solo una piccola osservazione: non è possibile utilizzare .then(console.log), poiché console.log dipende dal contesto della console. In questo modo causerà un errore di invocazione illegale. Utilizzare console.log.bind(console)o x => console.log(x)per associare il contesto.
Tamas Hegedus,

3
@hege_hegedus: ci sono ambienti in cui i consolemetodi sono già associati. E, naturalmente, ho solo detto che entrambi i nidificati hanno esattamente lo stesso comportamento, non che nessuno di loro funzionerebbe :-P
Bergi,

1
È stato perfetto. Questo è ciò di cui avevo bisogno: meno codice e più interpretazione. Grazie.
Adam Patterson,

21

In aggiunta alle risposte già esistenti, con funzioni ES6 freccia promesse girare da una modestamente brillante piccolo nano blu dritto in una gigante rossa. Sta per crollare in una supernova:

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

Come ha sottolineato Oligofren , senza argomenti tra le chiamate API non hai bisogno delle funzioni wrapper anonime:

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

E infine, se vuoi raggiungere un livello supermassiccio del buco nero, le promesse possono essere attese:

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

    return api3Result;
}

9
"con le funzioni della freccia ES6 Le promesse si trasformano da una piccola stella blu leggermente brillante direttamente in un gigante rosso.
Sta

3
Ciò fa sembrare Promesse come una catastrofe cosmica, che non credo fosse la tua intenzione.
Michael McGinnis,

Se non si utilizzano gli argomenti nei apiXmetodi, si potrebbe anche saltare le funzioni di direzione del tutto: api().then(api2).then(api3).then(r3 => console.log(r3)).
Oligofren,

@MichaelMcGinnis - L'impatto benefico di Promises su un noioso inferno di callback è come una supernova che esplode in un angolo buio dello spazio.
John Weisz,

So che lo intendi poeticamente, ma le promesse sono abbastanza lontane dalla "supernova". Mi viene in mente la violazione della legge monadica o la mancanza di supporto per casi d'uso più potenti come la cancellazione o la restituzione di più valori.
Dmitri Zaitsev il

15

Oltre alle fantastiche risposte di cui sopra, è possibile aggiungere altri 2 punti:

1. Differenza semantica:

Le promesse possono essere già risolte al momento della creazione. Ciò significa che garantiscono condizioni piuttosto che eventi . Se sono già stati risolti, la funzione risolta passata viene comunque chiamata.

Al contrario, i callback gestiscono gli eventi. Quindi, se l'evento che ti interessa è accaduto prima che il callback sia stato registrato, il callback non viene chiamato.

2. Inversione del controllo

I callback implicano l'inversione del controllo. Quando si registra una funzione di richiamata con qualsiasi API, il runtime Javascript memorizza la funzione di richiamata e la chiama dal ciclo degli eventi quando è pronta per essere eseguita.

Fare riferimento al loop degli eventi Javascript per una spiegazione.

Con Promises , il controllo risiede nel programma chiamante. Il metodo .then () può essere chiamato in qualsiasi momento se memorizziamo l'oggetto promessa.


1
Non so perché, ma questa sembra una risposta migliore.
Radiantshaw,

13

Oltre alle altre risposte, la sintassi ES2015 si fonde perfettamente con le promesse, riducendo ancora di più il codice del boilerplate:

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

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

5

Le promesse non sono callback, entrambi sono idiomi di programmazione che facilitano la programmazione asincrona. L'uso di uno stile di programmazione asincrono / waitit usando coroutine o generatori che restituiscono promesse potrebbe essere considerato un terzo idioma. Un confronto di questi modi di dire tra diversi linguaggi di programmazione (incluso Javascript) è qui: https://github.com/KjellSchubert/promise-future-task


5

No, per niente.

I callback sono semplicemente funzioni in JavaScript che devono essere richiamate e quindi eseguite al termine dell'esecuzione di un'altra funzione. Quindi come succede?

In realtà, in JavaScript, le funzioni sono esse stesse considerate oggetti e quindi, come tutti gli altri oggetti, anche le funzioni possono essere inviate come argomenti ad altre funzioni . Il caso d'uso più comune e generico che si possa pensare è la funzione setTimeout () in JavaScript.

Le promesse non sono altro che un approccio molto più improvvisato di gestione e strutturazione del codice asincrono rispetto a fare lo stesso con i callback.

Promessa riceve due callback nella funzione di costruzione: risoluzione e rifiuto. Questi callback all'interno delle promesse ci forniscono un controllo approfondito sulla gestione degli errori e sui casi di successo. Il callback di risoluzione viene utilizzato quando l'esecuzione della promessa viene eseguita correttamente e il callback di rifiuto viene utilizzato per gestire i casi di errore.


2

Nessuna promessa è solo un wrapper sui callback

esempio È possibile utilizzare promesse native javascript con il nodo 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

Le promesse JavaScript utilizzano effettivamente le funzioni di callback per determinare cosa fare dopo che una promessa è stata risolta o respinta, quindi entrambi non sono sostanzialmente diversi. L'idea principale alla base di Promises è quella di eseguire callback, in particolare callback nidificati in cui si desidera eseguire una sorta di azioni, ma sarebbe più leggibile.


0

Panoramica delle promesse:

In JS possiamo concludere le operazioni asincrone (ad esempio chiamate al database, chiamate AJAX) in promesse. Di solito vogliamo eseguire qualche logica aggiuntiva sui dati recuperati. Le promesse di JS hanno funzioni di gestione che elaborano il risultato delle operazioni asincrone. Le funzioni del gestore possono anche contenere al loro interno altre operazioni asincrone che potrebbero basarsi sul valore delle precedenti operazioni asincrone.

Una promessa ha sempre dei 3 seguenti stati:

  1. in sospeso: stato iniziale di ogni promessa, né adempiuto né respinto.
  2. completato: operazione completata correttamente.
  3. respinto: l'operazione non è riuscita.

Una promessa in sospeso può essere risolta / completata o rifiutata con un valore. Quindi vengono chiamati i seguenti metodi del gestore che accettano i callback come argomenti:

  1. Promise.prototype.then() : Quando la promessa viene risolta, viene chiamato l'argomento callback di questa funzione.
  2. Promise.prototype.catch() : Quando la promessa viene rifiutata, verrà chiamato l'argomento callback di questa funzione.

Sebbene l'abilità dei metodi di cui sopra ottenga argomenti di callback, essi sono di gran lunga superiori rispetto all'utilizzo dei soli callback qui è un esempio che chiarirà molto:

Esempio

function createProm(resolveVal, rejectVal) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (Math.random() > 0.5) {
                console.log("Resolved");
                resolve(resolveVal);
            } else {
                console.log("Rejected");
                reject(rejectVal);
            }
        }, 1000);
    });
}

createProm(1, 2)
    .then((resVal) => {
        console.log(resVal);
        return resVal + 1;
    })
    .then((resVal) => {
        console.log(resVal);
        return resVal + 2;
    })
    .catch((rejectVal) => {
        console.log(rejectVal);
        return rejectVal + 1;
    })
    .then((resVal) => {
        console.log(resVal);
    })
    .finally(() => {
        console.log("Promise done");
    });

  • La funzione createProm crea una promessa che viene risolta o respinta in base a un numero casuale dopo 1 secondo
  • Se la promessa viene risolta then, viene chiamato il primo metodo e il valore risolto viene passato come argomento del callback
  • Se la promessa viene rifiutata catch, viene chiamato il primo metodo e il valore rifiutato viene passato come argomento
  • I metodi catche thenrestituiscono promesse, ecco perché possiamo incatenarli. Avvolgono qualsiasi valore restituitoPromise.resolve e qualsiasi valore generato (usando la throwparola chiave) in Promise.reject. Quindi qualsiasi valore restituito viene trasformato in una promessa e su questa promessa possiamo chiamare nuovamente una funzione gestore.
  • Le catene di promesse ci offrono un controllo più preciso e una panoramica migliore rispetto ai callback nidificati. Ad esempio, il catchmetodo gestisce tutti gli errori che si sono verificati prima del catchgestore.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.