JavaScript Promise

JavaScript Promise (promise (англ.) - обещание) представляет собой результат операции, которая еще не была завершена, но ожидается её завершение в какой-то неопределенный момент в будущем. Примером такой операции является сетевой запрос. Когда мы извлекаем данные из некоторого источника, например API, не существует никакого способа для нас точно определить, когда ответ будет получен.

Это может стать проблемой если у нас есть другие операции, зависящие от завершения этого сетевого запроса. Без Promises, мы должны использовать функции обратного вызова (callbacks) для выполнения действий, которые должны произойти в определенной последовательности. Это не обязательно является проблемой, если у нас есть только одно асинхронное действие. Но если нам нужно выполнить несколько асинхронных шагов в определенной последовательности, то обратные вызовы выглядят запутанными, непонятными, то что печально известно как callback hell.

doSomething(function(responseOne) {  
    doSomethingElse(responseOne, function(responseTwo, err) {
        if (err) { handleError(err); }
        doMoreStuff(responseTwo, function(responseThree, err) {
            if (err) { handleAnotherError(err); }
            doFinalThing(responseThree, function(err) {
                if (err) { handleAnotherError(err); }
                // Complete
            }); // end doFinalThing
        }); // end doMoreStuff
    }); // end doSomethingElse
}); // end doSomething

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

doSomething()  
.then(doSomethingElse)
.catch(handleError)
.then(doMoreStuff)
.then(doFinalThing)
.catch(handleAnotherError)

Создание Promises

Промис создается с помощью конструктора Promise. Он принимает функцию с двумя аргументами (resolve & reject) в качестве единственного параметра.

var promise = new Promise( function(resolve, reject) { /* Promise content */ } )

Создание promises

Внутри функции мы можем выполнить любую асинхронную задачу, которую мы хотим. Чтобы пометить promise как выполненный (fullfilled), мы называем функцию resolve(), при необходимости передавая значение, которое мы хотим вернуть. Чтобы пометить promise как отклоненный или закончившийся ошибкой (rejected), мы вызываем функцию reject(), дополнительно можно передать в неё сообщение об ошибке. Перед тем, как promise перейдет в статус fullfilled или rejected, он находится в состоянии pending (ожидание).

Вот промифицированная версия запроса XMLHttpRequest:

function get(url) {  
  return new Promise(function(resolve, reject) {
    var req = new XMLHttpRequest();
    req.open('GET', url);
    req.onload = function() {
      if (req.status == 200) {
          resolve(req.response); /* PROMISE RESOLVED */
      } else {
          reject(Error(req.statusText)); /* PROMISE REJECTED */
      }
    };
    req.onerror = function() { reject(Error("Network Error")); };
    req.send();
  });
}

Использование Promises

После того, как мы создали обещание, мы должны использовать его. Для того, чтобы выполнить промисифицированную функцию, мы можем вызвать её как и любую обычную функцию. Но, поскольку это обещание, то мы теперь имеем доступ к методу .then(), который мы можем добавить к функции и который выполнится, когда обещание больше не будет в статусе pending.

Метод .then() принимает два необязательных параметра. Первый - это функция, которая вызывается, если обещание выполнено. Второй - функция, которая вызывается, если обещание будет отклонено.

get(url)  
.then(function(response) {
    /* successFunction */
}, function(err) {
    /* errorFunction */
})

Использование promises

Обработка ошибок

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

get(url)  
.then(function(response) {
    /* successFunction */
}, undefined)
.then(undefined, function(err) {
    /* errorFunction */
})

Чтобы сделать такие вещи еще более удобным для чтения, мы используем метод .catch(), который является сокращением для .then(undefined, errorFunction).

get(url)  
.then(function(response) {
    /* successFunction */
})
.catch(function(err) {
    /* errorFunction */
})

Обработка ошибок

"Чейнинг"

Реальная ценность промисов - это когда у нас есть несколько асинхронных функций, которые мы должны выполнить в определенном порядке. Мы можем объединить в цепочку методы .then() и .catch() вместе, чтобы создать последовательность асинхронных функций.

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

get(url)  
.then(function(response) {
    response = JSON.parse(response);
    var seondURL = response.data.url
    return get( seondURL ); /* Return another Promise */
})
.then(function(response) {
    response = JSON.parse(response);
    var thirdURL = response.data.url
    return get( thirdURL ); /* Return another Promise */
})
.catch(function(err) {
    handleError(err);
});

Если промис в цепочке решен (resolve), он будет переходить дальше к следующей функции .then (). Если, с другой стороны, обещание отклонено, промис будет переходить к следующей функции обработки ошибки (.catch ()).

Цепочка выполнения промисов

 

Параллельное выполнение обещаний

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

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

Во-вторых, метод Promise.all(), который возвращает обещание, которое будет решено (resolved) только тогда, когда каждое обещание в массиве будет решено. Если какое-либо одно обещание в пределах массива отклоняется, обещание Promise.all() также отклоняется.

var arrayOfURLs = ['one.json', 'two.json', 'three.json', 'four.json'];  
var arrayOfPromises = arrayOfURLs.map(get);

Promise.all(arrayOfPromises)  
.then(function(arrayOfResults) {
    /* Делаем что то когда все Promises решены */
})
.catch(function(err) {
    /* Обрабатываем ошибку если какой-либо из Promises завершился ошибкой */
})

Паралельное выполнение промисов

Если мы посмотрим в панели Сеть в Инструментах разработчика браузера, то мы можем видеть, что все запросы происходят параллельно.

Паралельные сетевые запросы

Поддержка браузеров

Поддержка браузерами

Текущую поддержку смотрите тут - http://caniuse.com/#feat=promises

Если вам нужна поддержка Internet Explorer или Opera Mini, вы можете использовать это Promise Polyfill.

Перевод статьи с bitsofco.de


Если у Вас возникли вопросы, то для скорейшего получения ответа рекомендуем воспользоваться нашим форумом

Комментарии:

Добавить комментарий


Защитный код
Обновить