ES6: Что дают промисы?

214
18 февраля 2018, 00:01

Думаю, я что-то недопонял с промисами. Вроде как промис - это средство для организации асинхронного кода. Но если сравнить, например, код для AJAX-запроса с промисом и код для AJAX-запроса без промиса, никакой организации кода нет: по сути reslove() и reject() - это всё те же обратные вызовы, которые мы можем сделать и без промиса. Отсюда резонный вопрос: что нам даёт промис?

Пример с промисом:

ajaxRequest = new Promise((resolve, reject) => {
    $.ajax({
        url: PATH_TO_PHP_SCRIPT,
        type: 'GET',
        data: dataWillBeSubmitted,
                success: response => {
                    resolve()
                },
                error: () => {
                    reject();
                }
    });
});

ajaxRequest
        .then(() => {
            // логика успешного запроса     
        })
        .catch(()=>{
            // логика ошибки запроса  
        })

Без промиса (даже короче получается):

$.ajax({
    url: PATH_TO_PHP_SCRIPT,
    type: 'GET',
    data: dataWillBeSubmitted,
            success: response => {
                successCallback()
            },
            error: () => {
                errorCallback();
            }
});

function successCallback(){
}
function errorCallback(){
}
Answer 1

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

Допустим, у нас есть вот такой код на колбеках, сложившийся исторически:

try {
    // ...
    if (success) {
         successCallback();
    } else {
         errorCallback();
    }
} catch {
    if (errorIsNormal) {
         successCallback();
    } else {
         errorCallback();
    }
}

Здесь, если внутри successCallback будет ошибка - то в некоторых случаях errorCallback будет сразу же вызван, а в некоторых - не будет. Кроме того, ошибка при выполнении errorCallback тоже может попасть снова в errorCallback - а может и не попасть... Конечно же, так код лучше не писать. Но в больших проектах подобная хренотень может встретиться просто по закону больших чисел.

В случае же с промисами все становится гораздо проще! Если обработчик написан вот так:

request
    .then(() => {
        // successCallback
    })
    .catch(()=>{
        // errorCallback
    })

То ошибка при выполнении successCallback всегда попадает в errorCallback, а ошибка из errorCallback - никогда. Независимо от того, насколько запутан вызываемый код - обработчики будут вызваны только 1 раз и в нужном порядке. С промисами код становится менее хрупким.

Но по-настоящему полезными промисы становятся если использовать их правильно! А правило очень простое: к конструктору промиса можно обращаться только при взаимодействии со старым кодом или в особых случаях.

Ваш пример с ajaxRequest можно переписать вот так:

ajaxRequest = $.ajax({
    url: PATH_TO_PHP_SCRIPT,
    type: 'GET',
    data: dataWillBeSubmitted
});

И все! $.ajax уже возвращает промис - нет необходимости использовать конструктор! Код уже выглядит проще, не так ли?

Но, допустим, вас не устраивает поведение промисов из jquery (а они ведут себя не совсем как класс Promise), и хочется получить стандартный промис. Это тоже довольно просто:

ajaxRequest = $.ajax({
    url: PATH_TO_PHP_SCRIPT,
    type: 'GET',
    data: dataWillBeSubmitted
});
ajaxRequest = Promise.resolve(ajaxRequest);

Все! Теперь у нас в переменной ajaxRequest самый обычный промис.

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

$.ajax({
    url: PATH_TO_PHP_SCRIPT,
    type: 'GET',
    data: dataWillBeSubmitted,
    success: response => {
        try {
            var data = JSON.parse(response.data);
        } catch (ex) {
            errorCallback(ex);
            return;
        }
        successCallback(data);
    },
    error: () => {
        errorCallback();
    }
});

А вот так это делается на промисах:

ajaxRequest = $.ajax({
    url: PATH_TO_PHP_SCRIPT,
    type: 'GET',
    data: dataWillBeSubmitted
})
    .then(response => JSON.parse(response.data));
Answer 2

Промисы помогают избавить от вложенности кода, когда мы один колбек вкладываем в другой и т.д. Воспринимать такой код и работать с ним удобнее.

Answer 3

Промисы придуманы не для создания асинхронного кода, а только для его использования. Например fetch, вроде он возвращает промис, а вроде это обычный XMLHttpRequest, а под капотом это XMLHttpRequest завернутый в промис. Если хотите узнать больше о магии Promis - советую к прочтению статью от mailru group

READ ALSO
Какие есть способы узнать, из какой функции был вызов, помимо Function.caller?

Какие есть способы узнать, из какой функции был вызов, помимо Function.caller?

В приведённом ниже коде machineMonthNumber - это номер месяца от 0 до 11 (в отличие от humanMonthNumber - номер месяца от 1 до 12)Функция validateMachineMonthNumber(machineMonthNumber),...

228
ckeditor плагин link - как запретить скрывать protocol?

ckeditor плагин link - как запретить скрывать protocol?

Настраиваю ckeditor, а точнее его плагин linkХочу сделать так, чтобы при вставке ссылки не прятался протокол (http:// , https:// , ftp://

221
Событие onclick на кнопке

Событие onclick на кнопке

Когда я нажимаю на кнопку, чтобы высвечивался alert с текстом кнопкиУ меня такой код, это правильно, или надо по-другому прописать?

226
Получить данные PHP

Получить данные PHP

Как получить href?

206