Разве обещания - это не просто обратные вызовы?

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

Похоже, все, что я делаю, это меняю:

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

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

Итак, что здесь такого большого шума из-за обещаний?


person Benjamin Gruenbaum    schedule 20.03.2014    source источник
comment
По теме: есть действительно информативная статья о Promises на Html5Rocks: html5rocks.com/en/tutorials/es6/promises   -  person ComFreek    schedule 20.03.2014
comment
Fyi, ответ, который вы приняли, - это тот же старый список тривиальных преимуществ, которые вообще не являются предметом обещаний и даже не убедили меня использовать обещания: /. Что убедило меня использовать обещания, так это аспект DSL, описанный в ответе Оскара.   -  person Esailija    schedule 31.03.2014
comment
@Esailija, ладно, твой голос убедил меня. Я принял другой ответ, хотя думаю, что ответ Берги также поднимает несколько действительно хороших (и разных) пунктов.   -  person Benjamin Gruenbaum    schedule 08.04.2014
comment
@Esailija Что убедило меня использовать обещания, так это аспект DSL, описанный в ответе Оскара ‹< Что такое DSL? и какой аспект DSL вы имеете в виду?   -  person monsto    schedule 03.03.2018
comment
@monsto: DSL: Domain Specific Language, язык, специально разработанный для использования в определенном подмножестве системы (например, SQL или ORM для взаимодействия с базой данных, регулярное выражение для поиска шаблонов и т. д.). В этом контексте DSL - это API-интерфейс Promise, который, если вы структурируете свой код так, как это сделал Оскар, почти похож на синтаксический сахар, который дополняет JavaScript для решения конкретного контекста асинхронных операций. Обещания создают некоторые идиомы, которые превращают их почти в язык, предназначенный для того, чтобы позволить программисту более легко понять несколько неуловимый мысленный поток структур этого типа.   -  person Michael Ekoka    schedule 11.04.2018


Ответы (10)


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

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

Надеюсь, теперь вы видите обещания в новом свете.

person Oscar Paz    schedule 20.03.2014
comment
Они действительно не должны были называть это обещанием. Будущее как минимум в 100 раз лучше. - person Pacerier; 02.05.2014
comment
@Pacerier, потому что будущее не было испорчено jQuery? - person Esailija; 31.01.2015
comment
Альтернативный шаблон (в зависимости от желаемого: api (). Then (api2) .then (api3) .then (doWork); То есть, если функции api2 / api3 принимают входные данные с последнего шага и сами возвращают новые обещания, они можно просто скрепить цепочкой без лишней обертывания, то есть составить. - person Dtipson; 22.12.2015
comment
Что, если в api2 и api3 есть асинхронные операции? будет ли последний .then вызываться только после завершения этих асинхронных операций? - person NiCk Newman; 24.01.2016
comment
@ scott-arciszewski: Если мы хотим, чтобы обещание-y выполнялось на основе выходных данных объекта обещания-x, а затем выполнялось обещание-z на основе выходных данных обещания-у, есть ли возможность это сделать? - person saylee; 29.04.2016
comment
Почему ты меня пометил? Я просто немного поправил грамматику. Я не эксперт по JS. :) - person Scott Arciszewski; 29.04.2016
comment
У меня проблема с определением: «будущий результат асинхронной операции». Разве это не обратный вызов? - person Jin; 11.11.2017
comment
Еще одно преимущество Promises - возможность присвоить переменной значение Promise. Это открывает возможность разделения бизнес-логики, которая не принадлежит друг другу, но имеет общую зависимость от этого конкретного вызова и т. Д. - person thisguyheisaguy; 01.02.2018
comment
Нет ли недостатков в использовании обещаний? Если да, следует ли мне по возможности использовать обещания вместо обратных вызовов? - person user2652379; 30.04.2018
comment
... истинные преимущества: ... проверить наличие ошибок на любом из шагов? - в равной степени этого можно добиться с помощью обратных вызовов: github.com/dmitriz/cpsfy - person Dmitri Zaitsev; 09.06.2019
comment
Еще одна вещь: обещания получают очередь событий с более высоким приоритетом stackoverflow.com/questions/19743354/ - person SPS; 20.12.2019
comment
Я нашел этот ответ одним из лучших примеров использования обещаний. Итак, я не понимаю, что такое сильное голосование против? (-6 на сегодня) - person Breaking not so bad; 27.03.2020

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

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

Так в чем же основная идея?

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

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

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

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

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

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

Что такого особенного в обещаниях?

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

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

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

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

person Bergi    schedule 21.03.2014
comment
привет Берги, не могли бы вы добавить что-нибудь интересное к этому ТАК вопросу? stackoverflow.com/questions/22724883/ - person Sebastien Lorber; 01.04.2014
comment
@Sebastien: Я мало что знаю о Scala (пока), и я мог только повторить то, что сказал Бенджамин :-) - person Bergi; 01.04.2014
comment
Извините, это довольно хороший ответ, но Петька (@Esailija) убедил меня, что другой вариант лучше подходит для людей, у которых есть эта конкретная проблема. - person Benjamin Gruenbaum; 08.04.2014
comment
Небольшое замечание: вы не можете использовать .then(console.log), поскольку console.log зависит от контекста консоли. Таким образом, это вызовет ошибку незаконного вызова. Используйте console.log.bind(console) или x => console.log(x) для привязки контекста. - person Tamas Hegedus; 20.11.2015
comment
@hege_hegedus: Есть среды, в которых console методы уже привязаны. И, конечно же, я только сказал, что оба вложения ведут себя одинаково, а не то, что любое из них будет работать :-P - person Bergi; 20.11.2015
comment
Это было отлично. Это то, что мне нужно: меньше кода и больше интерпретации. Спасибо. - person Adam Patterson; 05.03.2017
comment
Они не могут делать то, что не могут сделать обратные вызовы, хорошо ли это написано? - person Tiago Martins Peres 李大仁; 18.09.2020
comment
@Tiago Что бы вы написали вместо этого? Обратные вызовы могут делать все, что могут сделать обещания? - person Bergi; 18.09.2020
comment
@Bergi намного яснее да. Могу добавить и еще в конце тоже - person Tiago Martins Peres 李大仁; 18.09.2020

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

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

Как отметил oligofren, без аргументов между вызовами 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;
}
person John Weisz    schedule 12.09.2016
comment
с функциями стрелок ES6 Обещания превращаются из скромно сияющей маленькой голубой звездочки прямо в красного гиганта. Это вот-вот превратится в сверхновую. Перевод: Комбинация стрелочных функций ES6 с обещаниями - это круто :) - person user3344977; 16.06.2017
comment
Это заставляет Promises звучать как космическая катастрофа, что, я не думаю, было вашим намерением. - person Michael McGinnis; 31.10.2017
comment
Если вы не используете аргументы в apiX методах, вы можете вообще пропустить стрелочные функции: api().then(api2).then(api3).then(r3 => console.log(r3)). - person oligofren; 07.11.2017
comment
@MichaelMcGinnis - Благотворное влияние обещаний на унылый ад обратных вызовов похоже на взрывающуюся сверхновую в темном углу космоса. - person John Weisz; 24.05.2018
comment
Я знаю, что вы имеете в виду это поэтично, но обещания далеки от сверхновых. На ум приходят нарушение монадического закона или отсутствие поддержки более эффективных вариантов использования, таких как отмена или возврат нескольких значений. - person Dmitri Zaitsev; 09.06.2019

В дополнение к замечательным ответам выше можно добавить еще 2 балла:

1. Семантическая разница:

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

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

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

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

Обратитесь к циклу событий Javascript для объяснения. .

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

person Karthick    schedule 18.04.2018
comment
Не знаю почему, но это кажется лучшим ответом. - person radiantshaw; 26.02.2019
comment
Отлично, это - ›При использовании Promises управление осуществляется вызывающей программой. Метод .then () может быть вызван в любое время, если мы сохраняем объект обещания. - person HopeKing; 10.04.2021

В дополнение к другим ответам синтаксис 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
});
person Duncan Luk    schedule 23.07.2017

Нет, совсем нет.

Обратные вызовы - это просто функции в JavaScript, которые должны вызываться и затем выполняться после завершения выполнения другой функции. Так как же это происходит?

Фактически, в JavaScript функции сами считаются объектами и, следовательно, как и все другие объекты, даже функции могут быть отправлены в качестве аргументов другому функции. Самым распространенным и универсальным вариантом использования, который можно придумать, является функция setTimeout () в JavaScript.

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

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

person Ayush Jain    schedule 06.03.2019

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

person Kjell Schubert    schedule 01.04.2014

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

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

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

person Hamid Shoja    schedule 20.12.2019

Обзор обещаний:

В JS мы можем заключать асинхронные операции (например, вызовы базы данных, вызовы AJAX) в обещания. Обычно мы хотим запустить некоторую дополнительную логику для извлеченных данных. JS-обещания имеют функции-обработчики, которые обрабатывают результат асинхронных операций. Функции-обработчики могут даже иметь внутри себя другие асинхронные операции, которые могут полагаться на значение предыдущих асинхронных операций.

Обещание всегда имеет 3 следующих состояния:

  1. ожидание: начальное состояние каждого обещания, ни выполнено, ни отклонено.
  2. выполнено: операция успешно завершена.
  3. отклонено: операция не удалась.

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

  1. Promise.prototype.then(): Когда обещание будет разрешено, будет вызван аргумент обратного вызова этой функции.
  2. Promise.prototype.catch(): Когда обещание отклоняется, вызывается аргумент обратного вызова этой функции.

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

Пример

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

  • Функция createProm создает обещания, которые разрешаются или отклоняются на основе случайного Nr через 1 секунду.
  • Если обещание разрешено, вызывается первый then метод и разрешенное значение передается в качестве аргумента обратного вызова.
  • Если обещание отклоняется, вызывается первый catch метод, и отклоненное значение передается в качестве аргумента.
  • Методы catch и then возвращают обещания, поэтому мы можем объединить их в цепочку. Они заключают любое возвращаемое значение в Promise.resolve и любое выброшенное значение (с использованием ключевого слова throw) в Promise.reject. Таким образом, любое возвращаемое значение преобразуется в обещание, и для этого обещания мы снова можем вызвать функцию-обработчик.
  • Цепочки обещаний дают нам более точный контроль и лучший обзор, чем вложенные обратные вызовы. Например, метод catch обрабатывает все ошибки, которые произошли до обработчика catch.
person Willem van der Veen    schedule 06.05.2020