Разве обещания не являются просто обратными вызовами?


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

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

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

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

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

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

Так, что большая суета вокруг обещаний здесь?

7 355

7 ответов:

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

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

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

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

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-вызовами), но вам нужно было ждать трех? Без обещаний, вы должны создать какой-то счетчик. С обещаниями, используя обозначение 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.
});

надеюсь, вы видите обещания в новом свете сейчас.

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

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

так в чем же главная идея?

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

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

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

обещания змеевидных (монадическом, если вы хотите):

  • Если вам нужно преобразовать значение, которое представляет обещание, вы карта функция преобразования над обещанием и получить обратно новое обещание, которое представляет преобразованный результат. Вы не можете одновременно получить значение, чтобы использовать его как-то, но вы можете легко лифт трансформация в контексте обещание. Никаких шаблонных обратных вызовов.
  • если вы хотите связать две асинхронные задачи, вы можно использовать .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)

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

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

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

что за шумиха вокруг обещаний?

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

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

не говоря уже о том, чтобы конвертировать вещи в обещает выделить.

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

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

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

как олигофрен указано, что без аргументов между вызовами api вам вообще не нужны анонимные функции-оболочки:

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

и наконец, если вы хотите достичь уровня сверхмассивная черная дыра, обещания могут быть ждали:

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

    return api3Result;
}

в дополнение к другим ответам синтаксис ES2015 легко сочетается с обещаниями, уменьшая еще больше шаблонного кода:

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

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

в дополнение к удивительным ответам выше, могут быть добавлены еще 2 очка:

1. Семантическое различие:

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

наоборот, обратные вызовы обработка событий. Итак, если событие, которое вас интересует, произошло до обратного вызова был зарегистрирован, обратный вызов не вызывается.

2. Инверсия управления

обратные вызовы включить инверсию управления. Когда вы регистрируете функцию обратного вызова с любым API, среда выполнения Javascript сохраняет функцию обратного вызова и вызывает ее из цикла событий, как только она готова к запуску.

см. цикл событий Javascript для объяснений.

с обещания, управление проживает с помощью вызывающей программы. The .тогда() метод может быть вызван в любое время если мы храним объект Promise.

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

никакие обещания не просто обертка на обратные вызовы

пример Вы можете использовать собственные обещания javascript с узлом 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