¿No son las promesas solo devoluciones de llamada?

430

He estado desarrollando JavaScript durante algunos años y no entiendo el alboroto sobre las promesas en absoluto.

Parece que todo lo que hago es cambiar:

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

Para lo cual podría usar una biblioteca como async de todos modos, con algo como:

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

Que es más código y menos legible. No gané nada aquí, tampoco es mágicamente 'plano' de repente. Sin mencionar tener que convertir las cosas en promesas.

Entonces, ¿cuál es el gran alboroto por las promesas aquí?

Benjamin Gruenbaum
fuente
11
Sobre el tema : hay un artículo realmente informativo sobre Promesas en Html5Rocks: html5rocks.com/en/tutorials/es6/promises
ComFreek
2
Para su información, la respuesta que aceptó es la misma lista de los beneficios triviales que no son el objetivo de las promesas y ni siquiera me convencieron de usar promesas: /. Lo que me convenció de usar promesas fue el aspecto DSL como se describe en la respuesta de Oscar
Esailija
@Esailija bien, tu discurso haberte convencido. Acepté la otra respuesta, aunque creo que la de Bergi también plantea algunos puntos muy buenos (y diferentes).
Benjamin Gruenbaum
@Esailija "Lo que me convenció de usar promesas fue el aspecto DSL como se describe en la respuesta de Oscar" << ¿Qué es "DSL"? ¿Y cuál es el "aspecto DSL" al que te refieres?
Monsto
1
@monsto: DSL: lenguaje específico de dominio, un lenguaje diseñado específicamente para ser utilizado en un subconjunto particular de un sistema (por ejemplo, SQL u ORM para hablar con la base de datos, expresiones regulares para encontrar patrones, etc.). En este contexto, el "DSL" es la API de Promise que, si estructura su código como lo hizo Oscar, es casi como el azúcar sintáctico que complementa a JavaScript para abordar el contexto particular de las operaciones asíncronas. Las promesas crean algunas expresiones idiomáticas que las convierten en casi un lenguaje diseñado para permitir que el programador capte más fácilmente el flujo mental algo elusivo de este tipo de estructuras.
Michael Ekoka

Respuestas:

631

Las promesas no son devoluciones de llamada. Una promesa representa el resultado futuro de una operación asincrónica . Por supuesto, al escribirlos de la manera que lo haces, obtienes pocos beneficios. Pero si los escribe de la forma en que deben usarse, puede escribir código asincrónico de una manera que se parezca a un código síncrono y sea mucho más fácil de seguir:

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

Ciertamente, no mucho menos código, pero mucho más legible.

Pero, este no es el final. Descubramos los verdaderos beneficios: ¿Qué sucede si desea verificar cualquier error en alguno de los pasos? Sería un infierno hacerlo con devoluciones de llamada, pero con promesas, es pan comido:

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

Más o menos lo mismo que un try { ... } catchbloque.

Aun mejor:

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

Y aún mejor: ¿Qué pasa si esas llamadas a 3 api, api2, api3podían ejecutar de forma simultánea (por ejemplo, si fueran llamadas AJAX) pero necesitan que esperar a los tres? Sin promesas, deberías tener que crear algún tipo de contador. Con promesas, usar la notación ES6 es otro pedazo de pastel y bastante bueno:

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

Espero que veas las promesas bajo una nueva luz ahora.

Oscar Paz
fuente
124
Realmente no deberían haberlo nombrado como "Promesa". "Futuro" es al menos 100 veces mejor.
Pacerier
12
@Pacerier porque el futuro no fue contaminado por jQuery?
Esailija
55
Patrón alternativo (dependiendo de lo que se desee: api (). Luego (api2) .then (api3) .then (doWork); Es decir, si las funciones api2 / api3 reciben información del último paso y devuelven nuevas promesas, simplemente se pueden encadenar sin envoltura adicional. Es decir, componen.
Dtipson
1
¿Qué pasa si hay operaciones asíncronas en api2y api3? ¿se llamaría al último .thenuna vez que se completen esas operaciones asincrónicas?
NiCk Newman
8
¿Por qué me etiquetaste? Acabo de arreglar la gramática un poco. No soy un experto en JS. :)
Scott Arciszewski
169

Sí, las promesas son devoluciones de llamada asincrónicas. No pueden hacer nada que las devoluciones de llamada no puedan hacer, y usted enfrenta los mismos problemas con la asincronía que con las devoluciones de llamada simples.

Sin embargo, las promesas son más que simples devoluciones de llamada. Son una abstracción muy poderosa, permiten un código funcional más limpio y mejor con menos repeticiones propensas a errores.

Entonces, ¿cuál es la idea principal?

Las promesas son objetos que representan el resultado de un cálculo único (asíncrono). Ellos resuelven a ese resultado sólo una vez. Hay algunas cosas que significa esto:

Las promesas implementan un patrón de observación:

  • No necesita conocer las devoluciones de llamada que utilizarán el valor antes de que se complete la tarea.
  • En lugar de esperar devoluciones de llamada como argumentos de sus funciones, puede fácilmente returnun objeto Promise
  • La promesa almacenará el valor, y puede agregar de forma transparente una devolución de llamada cuando lo desee. Se llamará cuando el resultado esté disponible. La "transparencia" implica que cuando tienes una promesa y le agregas una devolución de llamada, no hace ninguna diferencia en tu código si el resultado ya ha llegado: la API y los contratos son los mismos, lo que simplifica mucho el almacenamiento en caché / memorización.
  • Puede agregar múltiples devoluciones de llamada fácilmente

Las promesas son encadenables ( monádicas , si quieres ):

  • Si necesita transformar el valor que representa una promesa, asigna una función de transformación sobre la promesa y obtiene una nueva promesa que representa el resultado transformado. No puede obtener sincrónicamente el valor para usarlo de alguna manera, pero puede levantar fácilmente la transformación en el contexto de la promesa. No hay devoluciones de llamada repetitivas.
  • Si desea encadenar dos tareas asincrónicas, puede usar el .then()método. Se necesitará una devolución de llamada para ser llamada con el primer resultado, y devuelve una promesa para el resultado de la promesa de que la devolución de llamada regresa.

¿Suena complicado? Tiempo para un ejemplo de código.

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)

El aplanamiento no viene mágicamente, pero puedes hacerlo fácilmente. Para su ejemplo muy anidado, el equivalente (cercano) sería

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

Si ver el código de estos métodos ayuda a comprender, aquí hay una promesa más básica en pocas líneas .

¿Cuál es el gran alboroto sobre las promesas?

La abstracción de Promise permite una mejor composibilidad de funciones. Por ejemplo, junto a thenencadenar, la allfunción crea una promesa para el resultado combinado de múltiples promesas de espera paralela.

Por último, pero no menos importante, las promesas vienen con un manejo integrado de errores. El resultado del cálculo podría ser que la promesa se cumple con un valor o se rechaza con un motivo. Todas las funciones de composición manejan esto automáticamente y propagan errores en las cadenas de promesa, para que no tenga que preocuparse explícitamente en todas partes, en contraste con una implementación de devolución de llamada simple. Al final, puede agregar una devolución de llamada de error dedicada para todas las excepciones ocurridas.

Sin mencionar tener que convertir las cosas en promesas.

Eso es bastante trivial en realidad con buenas bibliotecas de promesa, vea ¿Cómo convierto una API de devolución de llamada existente en promesas?

Bergi
fuente
hola Bergi, ¿te gustaría agregar algo interesante a esta pregunta SO? stackoverflow.com/questions/22724883/…
Sebastien Lorber
1
@Sebastien: No sé mucho sobre Scala (todavía), y solo pude repetir lo que dijo Benjamin :-)
Bergi
3
Solo un pequeño comentario: no se puede usar .then(console.log), ya que console.log depende del contexto de la consola. De esta forma, provocará un error de invocación ilegal. Use console.log.bind(console)o x => console.log(x)para enlazar contexto.
Tamas Hegedus
3
@hege_hegedus: Hay entornos donde los consolemétodos ya están vinculados. Y, por supuesto, solo dije que ambos anidamientos tienen exactamente el mismo comportamiento, no que ninguno de ellos funcionaría :-P
Bergi
1
Eso fue genial. Esto es lo que necesitaba: menos código y más interpretación. Gracias.
Adam Patterson
21

Además de las respuestas ya establecidas, con funciones ES6 flecha promesas de convertir una pequeña enana azul brillante modestamente recta en una gigante roja. Eso está a punto de colapsar en una supernova:

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

Como señaló oligofren , sin argumentos entre las llamadas de API, no necesita las funciones de contenedor anónimo en absoluto:

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

Y, por último, si quieres alcanzar un nivel de agujero negro supermasivo, puedes esperar promesas:

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

    return api3Result;
}
John Weisz
fuente
99
"con ES6 flecha funciones promesas a su vez de una pequeña estrella azul brillante recta modestamente en una gigante roja que está a punto de colapso en una supernova." Traducción: La combinación de ES6 flecha funciones con promesas es impresionante :)
user3344977
3
Eso hace que Promesas suene como una catástrofe cósmica, que no creo que sea tu intención.
Michael McGinnis
Si no está utilizando los argumentos a los apiXmétodos, que también podría omitir por completo las funciones de dirección: api().then(api2).then(api3).then(r3 => console.log(r3)).
oligofren
@MichaelMcGinnis: el impacto beneficioso de Promises en un aburrido infierno de devolución de llamada es como una supernova en explosión en un rincón oscuro del espacio.
John Weisz
Sé que lo dices poéticamente, pero las promesas están bastante lejos de ser "supernova". El incumplimiento de la ley monádica o la falta de soporte para casos de uso más potentes como la cancelación o la devolución de múltiples valores me vienen a la mente.
Dmitri Zaitsev
15

Además de las impresionantes respuestas anteriores, se pueden agregar 2 puntos más:

1. Diferencia semántica:

Las promesas pueden estar resueltas en el momento de la creación. Esto significa que garantizan condiciones en lugar de eventos . Si ya están resueltos, la función resuelta que se le pasa todavía se llama.

Por el contrario, las devoluciones de llamada manejan eventos. Por lo tanto, si el evento que le interesa ha sucedido antes de que se registre la devolución de llamada, no se llama a la devolución de llamada.

2. Inversión de control

Las devoluciones de llamada implican la inversión del control. Cuando registra una función de devolución de llamada con cualquier API, el tiempo de ejecución de Javascript almacena la función de devolución de llamada y la llama desde el bucle de eventos una vez que está lista para ejecutarse.

Consulte el bucle de eventos de Javascript para obtener una explicación.

Con Promesas , el control reside en el programa de llamadas. Se puede llamar al método .then () en cualquier momento si almacenamos el objeto de promesa.

dww
fuente
1
No sé por qué, pero esta parece ser una mejor respuesta.
radiantshaw
13

Además de las otras respuestas, la sintaxis ES2015 combina a la perfección con las promesas, reduciendo aún más el código repetitivo:

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

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

Las promesas no son devoluciones de llamada, ambas son expresiones idiomáticas de programación que facilitan la programación asíncrona. El uso de una programación de estilo asíncrono / en espera utilizando corutinas o generadores que devuelven promesas podría considerarse un tercer idioma. Una comparación de estos modismos en diferentes lenguajes de programación (incluido Javascript) está aquí: https://github.com/KjellSchubert/promise-future-task

Kjell Schubert
fuente
5

No, en absoluto.

Las devoluciones de llamada son simplemente funciones en JavaScript que deben llamarse y luego ejecutarse después de que la ejecución de otra función haya finalizado. Entonces, ¿cómo sucede?

En realidad, en JavaScript, las funciones se consideran en sí mismas como objetos y, por lo tanto, como todos los demás objetos, incluso las funciones se pueden enviar como argumentos a otras funciones . El caso de uso más común y genérico en el que uno puede pensar es la función setTimeout () en JavaScript.

Las promesas no son más que un enfoque mucho más improvisado de manejo y estructuración de código asincrónico en comparación con hacer lo mismo con las devoluciones de llamada.

La promesa recibe dos devoluciones de llamada en la función de constructor: resolver y rechazar. Estas devoluciones de llamada dentro de las promesas nos proporcionan un control detallado sobre el manejo de errores y los casos de éxito. La devolución de llamada de resolución se usa cuando la ejecución de la promesa se realizó con éxito y la devolución de llamada de rechazo se usa para manejar los casos de error.

Ayush Jain
fuente
2

No hay promesas son solo envoltorios en devoluciones de llamadas

ejemplo Puede usar las promesas nativas de JavaScript con el 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
Apoorv
fuente
1

Las Promesas de JavaScript en realidad usan funciones de devolución de llamada para determinar qué hacer después de que una Promesa se haya resuelto o rechazado, por lo tanto, ambas no son fundamentalmente diferentes. La idea principal detrás de Promises es tomar devoluciones de llamada, especialmente devoluciones de llamada anidadas donde desea realizar un tipo de acciones, pero sería más legible.

Hamid Shoja
fuente
0

Resumen de promesas:

En JS podemos envolver operaciones asincrónicas (por ejemplo, llamadas a bases de datos, llamadas AJAX) en promesas. Por lo general, queremos ejecutar una lógica adicional en los datos recuperados. Las promesas de JS tienen funciones de controlador que procesan el resultado de las operaciones asincrónicas. Las funciones del controlador pueden incluso tener otras operaciones asincrónicas dentro de ellas que podrían depender del valor de las operaciones asincrónicas anteriores.

Una promesa siempre tiene de los siguientes 3 estados:

  1. pendiente: estado inicial de cada promesa, ni cumplida ni rechazada.
  2. cumplido: la operación se completó correctamente.
  3. rechazado: la operación falló.

Una promesa pendiente puede resolverse / cumplirse o rechazarse con un valor. Luego se llaman los siguientes métodos de controlador que toman las devoluciones de llamada como argumentos:

  1. Promise.prototype.then() : Cuando se resuelve la promesa, se llamará al argumento de devolución de llamada de esta función.
  2. Promise.prototype.catch() : Cuando se rechaza la promesa, se llamará al argumento de devolución de llamada de esta función.

Aunque las habilidades de métodos anteriores obtienen argumentos de devolución de llamada, son muy superiores a usar solo devoluciones de llamada. Aquí hay un ejemplo que aclarará mucho:

Ejemplo

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 función createProm crea promesas que se resuelven o rechazan en función de un número aleatorio después de 1 segundo
  • Si la promesa se resuelve el primero then método y el valor resuelto se pasa como argumento de la devolución de llamada
  • Si se rechaza la promesa, se llama al primer catchmétodo y el valor rechazado se pasa como argumento
  • Los métodos catchy thendevuelven promesas, por eso podemos encadenarlos. Envuelven cualquier valor devuelto Promise.resolvey cualquier valor arrojado (usando la throwpalabra clave) en Promise.reject. Por lo tanto, cualquier valor devuelto se transforma en una promesa y en esta promesa podemos llamar nuevamente a una función de controlador.
  • Las cadenas de promesa nos brindan un control más preciso y una mejor visión general que las devoluciones de llamada anidadas. Por ejemplo, el catchmétodo maneja todos los errores que ocurrieron antes del catchcontrolador.
Willem van der Veen
fuente