Про­ми­сы на при­ме­рах из жиз­ни

Перевод «JavaScript: promises explained with simple real life analogies»

Перевод Артур Храбров

Редактура Вадим Макеев

Ай промис, фото Бена Уайта.

Промисы простыми словами Скопировать ссылку

Представьте это как разговор между двумя людьми:

Алекс: Эй, мистер Промис! Можешь сбегать в магазин и принести мне itemA для блюда, которое мы приготовим сегодня вечером? Промис: Отличная мысль! Алекс: Пока ты бегаешь, я подготовлю itemB (асинхронная операция). Только обязательно скажи, нашел ли ты itemA (возвращаемое промисом значение). Промис: А если тебя не будет дома, когда я вернусь? Алекс: Тогда отправь мне смску, что ты вернулся и принес item для меня (успешный колбэк). Если ты не найдешь, позвони мне немедленно (неуспешный колбэк). Промис: Отлично! Увидимся позже!

Проще говоря, объект promise — это данные, возвращаемые асинхронной функцией. Это может быть resolve, если функция прошла успешно или reject, если функция вернула ошибку.

Определение Скопировать ссылку

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

Промис в JS Скопировать ссылку

Давайте сначала поговорим о JavaScript и его параллелизме. JavaScript является однопоточным. Всё происходит в той последовательности, в которой написано, но асинхронные операции происходят в порядке их завершения.

Что, по вашему мнению, выведется в консоль в следующем примере?

console.log('1');
setTimeout(function(){ console.log('2'); }, 3000);

console.log('3');
setTimeout(function(){ console.log('4'); }, 1000);

Результатом будет 1 3 4 2. Вы можете задаться вопросом, почему 4 встречается раньше чем 2. Причина в том, что, несмотря на то, что строка с 2 описана раньше, она начала выполняться только после 3000 мс, поэтому 4 выводится до 2.

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

Теперь рассмотрим, как создать промис в JavaScript:

var promise = new Promise(function(resolve, reject) {
    // Делаем, что-то, возможно асинхронное, тогда…

    if (/* Всё прошло отлично */) {
        resolve('Сработало!');
    }
    else {
        reject(Error('Сломалось'));
    }
});

Конструктор Promise принимает один аргумент: колбэк с двумя параметрами — resolve и reject. Этот промис может быть использован следующим образом:

promise.then(function(result) {
    console.log('Промис сработал');
}, function(err) {
    console.log('Что-то сломалось');
});

Если промис прошёл успешно, будет выполнен resolve, и консоль выведет Промис сработал, в противном случае выведется Что-то сломалось. Это состояние до получения resolve или reject называется состоянием ожидания, pending. Таким образом, есть три состояния промиса:

  1. Ожидание ответа: pending.
  2. Успешное выполнение: resolve.
  3. Выход ошибкой: reject.
Промис успешно выполнился, фото Скотта Вебба.

Пример Скопировать ссылку

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

Сначала создадим промис с XMLHttpRequest:

const loadImage = url => {
    return new Promise(function(resolve, reject) {
        // Открываем новый XHR
        var request = new XMLHttpRequest();
        request.open('GET', url);

        // После загрузки запроса
        // проверяем, был ли он успешным
        request.onload = function() {
            if (request.status === 200) {
                // Если успешный, то резолвим промис
                resolve(request.response);
            } else {
                // Если нет, то реджектим промис
                reject(Error(
                    'Произошла ошибка. Код ошибки:' + request.statusText
                ));
            }
        };

        request.send();
    });
};

Теперь, когда изображение успешно загружено, промис вернет resolve с ответом от XHR. Давайте используем этот промис, вызвав функцию loadImage.

const embedImage = url => {
    loadImage(url).then(function(result) {
        const img = new Image();
        var imageURL = window.URL.createObjectURL(result);
        img.src = imageURL;
        document.querySelector('body').appendChild(img);
    },
    function(err) {
        console.log(err);
    });
}

Мы сделали это! Неплохо, да?

А теперь сделай несколько промисов сам! Давай :)

Дополнительные материалы Скопировать ссылку

Вот некоторые статьи, которые показались мне очень полезным в процессе обучения:

  1. Статья про промисы на MDN
  2. Введение на Google Developers
  3. Про concurrency model на MDN