Не являются ли promises только обратные вызовы?

268 Benjamin Gruenbaum [2014-03-20 19:49:00]

Я разрабатываю JavaScript в течение нескольких лет, и я вообще не понимаю суеты о promises.

Кажется, что все, что я делаю, это изменение:

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

Для чего я мог бы использовать библиотеку, например async, с чем-то вроде:

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

Что больше кода и менее читаемо. Я ничего здесь не получил, это не неожиданно волшебным образом "плоское". Не говоря уже о том, чтобы преобразовать вещи в promises.

Итак, какова большая суета о promises здесь?

javascript promise callback bluebird q


6 ответов


427 Решение Oscar Paz [2014-03-20 20:07:00]

Promises не являются обратными вызовами. Обещание представляет собой будущий результат асинхронной операции. Конечно, записывая их так, как вы, вы получаете небольшую выгоду. Но если вы напишете их так, как они предназначены для использования, вы можете написать асинхронный код таким образом, который напоминает синхронный код и гораздо проще:

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

Конечно, не намного меньше кода, но гораздо более читабельным.

Но это еще не конец. Откройте для себя истинные преимущества: что, если вы хотите проверить какую-либо ошибку на любом из шагов? Было бы ад, чтобы сделать это с помощью обратных вызовов, но с promises, является частью торта:

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

Совсем аналогично блоку try { ... } catch.

Еще лучше:

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

И еще лучше: что делать, если эти 3 вызова api, api2, api3 могут выполняться одновременно (например, если они были вызовами AJAX), но вам нужно было дождаться трех? Без promises вам нужно создать какой-то счетчик. С помощью promises, используя нотацию ES6, это еще один кусок торта и довольно аккуратный:

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

Надеюсь, вы увидите Promises в новом свете.


122 Bergi [2014-03-21 17:50:00]

Да, Promises - асинхронные обратные вызовы. Они не могут делать ничего, что вызовы не могут сделать, и вы сталкиваетесь с теми же проблемами с асинхронностью, что и с обычными обратными вызовами.

Однако Promises больше, чем просто обратные вызовы. Они представляют собой очень мощную абстракцию, обеспечивают более чистый и улучшенный функциональный код с менее подверженным ошибкам шаблоном.

Итак, какая основная идея?

Promises - это объекты, представляющие результат одного (асинхронного) вычисления. Они разрешают этот результат только один раз. Есть несколько вещей, что это означает:

Promises реализовать шаблон наблюдателя:

  • Вам не нужно знать обратные вызовы, которые будут использовать значение до завершения задачи.
  • Вместо того, чтобы ожидать обратных вызовов в качестве аргументов для ваших функций, вы можете легко return объект Promise
  • Обещание сохранит значение, и вы сможете прозрачно добавлять обратный вызов, когда захотите. Он будет вызываться, когда результат будет доступен. "Transparency" подразумевает, что когда у вас есть обещание и добавление обратного вызова к нему, это не влияет на ваш код, был ли результат еще получен - API и контракты одинаковы, что упрощает кэширование /memoisation.
  • Вы можете легко добавить несколько обратных вызовов

Promises являются цепями (монадические, если хотите):

  • Если вам нужно преобразовать значение, представляемое обещанием, вы сопоставляете функцию преобразования по обещанию и возвращаете новое обещание, которое представляет преобразованный результат. Вы не можете синхронно получить значение, чтобы использовать его каким-то образом, но вы можете легко поднять трансформацию в контексте обещания. Нет обратных вызовов шаблонов.
  • Если вы хотите связать две асинхронные задачи, вы можете использовать метод .then(). Это будет вызвано обратным вызовом с первым результатом и возвращает обещание для результата обещания возврата обратного вызова.

Звучит сложно? Время для примера кода.

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)

Сглаживание не приходит волшебным образом, но вы можете легко это сделать. Для вашего сильно вложенного примера эквивалент (ближайший) будет

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

Если просмотр кода этих методов помогает понять, здесь самое основное обещание lib в нескольких строках.

Какая большая проблема о promises?

Абстракция Promise позволяет значительно лучше выполнять функции. Например, рядом с then для цепочки функция all создает обещание для комбинированного результата многократного параллельного ожидания promises.

И последнее, но не менее важное: Promises поставляется с интегрированной обработкой ошибок. Результатом вычисления может быть то, что либо обещание выполняется со значением, либо оно отклоняется по причине. Все функции композиции обрабатывают это автоматически и распространяют ошибки в цепочках обещаний, так что вам не нужно заботиться об этом явно везде - в отличие от реализации простого обратного вызова. В конце вы можете добавить выделенный обратный вызов для всех произошедших исключений.

Не говоря уже о необходимости конвертировать вещи в promises.

Это довольно тривиально с хорошими библиотеками обещаний, см. Как преобразовать существующий API обратного вызова в promises?


9 John Weisz [2016-09-12 23:25:00]

В дополнение к удивительным ответам Оскар и Берги с функциями стрелок ES6 Promises поворот от скромно сияющей маленькой голубой звезды прямо к красному гиганту. Это вот-вот рухнет в сверхновой:

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

5 Duncan Luk [2017-07-23 16:24:00]

В дополнение к другим ответам синтаксис ES2015 легко смешивается с promises, сокращая еще более шаблонный код:

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

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

3 Kjell Schubert [2014-04-01 17:09:00]

Promises не являются обратными вызовами, оба являются идиомами программирования, которые облегчают асинхронное программирование. Использование асинхронного/ожидающего стиля программирования с помощью сопрограмм или генераторов, которые возвращают promises, можно считать третьей такой идиомой. Сравнение этих идиом на разных языках программирования (включая Javascript) можно найти здесь: https://github.com/KjellSchubert/promise-future-task


2 Apoorv [2016-06-20 16:42:00]

Нет promises - это просто оболочка при обратных вызовах

Пример   Вы можете использовать javascript native promises с node 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