В моей предыдущей статье о новом JavaScript Promise API обсуждались основы обещаний, обработки ошибок и концепции цепочек. Часто бывает необходимо объединить Promises в очередь для асинхронных операций. Но во многих случаях нам необходимо отслеживать порядок выполнения каждой задачи, чтобы выполнить следующие операции соответственно. Поскольку асинхронные задачи могут выполняться в любом порядке, поддержание последовательности при выполнении асинхронных операций может быть сложной задачей. В данной статье предпринята попытка разбить эти понятия на детали.
Пристальный взгляд на цепочку обещаний
Мы уже видели, как связать обещания с помощью then()
Теперь давайте разберемся, что действительно происходит, когда мы вызываем then()
Рассмотрим следующий код:
var newPromise = getPromise(someData).then(function(data) { // Line 1
return getPromise(data); //Line 2
}).then(function(data){ //Line 3
//use this data
});
Предположим, что getPromise()
Promise
Обратите внимание, что тип возвращаемого значения then()
Promise
В предыдущем примере строка 1 возвращает новое Promise
Мы также передали обратный вызов then()
Значение, возвращаемое обратным вызовом, используется для выполнения или отклонения обещания. Но если обратный вызов возвращает другое Promise
Promise
then()
Promise
Мы также приковали цепочку к другому then()
Promise
Promise
Вы можете продолжать цепочку Promises
Если вам нужно обработать любые исключения, вы можете добавить catch()
Теперь, когда вы знаете, как работает цепочка Promise, мы можем перейти к тому, как асинхронные операции можно выполнять по порядку. Но перед этим вам нужно понять еще несколько вещей.
Методы resolve()
reject()
API Promise предоставляет несколько полезных методов, облегчающих нашу жизнь. Одним из них является resolve()
Promise
Это означает, что если вы создадите Promise
then()
Вы также можете передать аргумент метода resolv resolve()
Если ничего не передано, значение выполнения не undefined
Точно так же, reject()
Promise
В следующем примере показано, как используются resolve()
reject()
Promise.resolve('this always resolves').then(function(data) {
alert(data); //this is called
});
Promise.reject('this always rejects').then(function(data) {
alert(data); // this is never called
}).catch(function(err) {
alert(err); //this is called
});
Обеспечение последовательного выполнения задач
Давайте создадим простое приложение, которое принимает список названий фильмов и выбирает постеры для каждого из них. Вот HTML-разметка, которая показывает поле ввода для ввода разделенных запятыми названий фильмов:
<!DOCTYPE html>
<html>
<head>
<script src="script.js"></script>
</head>
<body>
<input type="text" name="titles" id="titles" placeholder="comma separated movie titles" size="30"/>
<input type="button" value="fetch" onclick="fetchMovies()" />
<input type="button" value="clear" onclick="clearMovies()" />
<div id="movies">
</div>
</body>
</html>
Теперь давайте используем Promises для асинхронной загрузки постера для каждого фильма. Следующая функция создает Promise
function getMovie(title) {
return new Promise(function(resolve, reject) {
var request = new XMLHttpRequest();
request.open('GET', 'http://mymovieapi.com/?q=' + title);
request.onload = function() {
if (request.status == 200) {
resolve(request.response); // we get the data here, so resolve the Promise
} else {
reject(Error(request.statusText)); // if status is not 200 OK, reject.
}
};
request.onerror = function() {
reject(Error("Error fetching data.")); // error occurred, so reject the Promise
};
request.send(); // send the request
});
}
Следующий фрагмент обрабатывает загруженную информацию и обновляет HTML-страницу с постером фильма.
function fetchMovies() {
var titles = document.getElementById('titles').value.split(',');
for (var i in titles) {
getMovie(titles[i]).then(function(data) {
var img = JSON.parse(data)[0].poster.imdb;
document.getElementById('movies').innerHTML = document.getElementById('movies').innerHTML + '<img src="' + img + '"/>';
}).catch(function(error) {
console.log(error);
});
}
}
Предыдущий код довольно понятен. Он просто просматривает список названий фильмов и извлекает постер IMDB для каждого из них. Вы можете проверить этот пример Plunkr, чтобы увидеть код в действии.
Но существует проблема! В примере с Plunkr введите названия фильмов, разделенные запятыми, и нажмите кнопку «Получить». Если вы нажмете «Получить» несколько раз, вы поймете, что нет определенного порядка загрузки изображений! Обещания могут быть выполнены в любом порядке, и поэтому наши изображения также каждый раз приходят в другом порядке. Таким образом, этот код не будет служить нашей цели, если нам нужно выбрать постеры фильма в определенном порядке.
Мы можем обеспечить порядок двумя способами. Во-первых, мы можем создать Promise
Promise
Второй способ заключается в создании отдельного Promise
Promise
Опция 1
Посмотрите на следующий фрагмент. Мы начинаем с создания Promise
Это используется для отслеживания предыдущего Promise
Внутри цикла мы вызываем prevPromise.then()
Promise
prevPromise
Это Promise
Promise
getMovie(title)
Таким образом, Promise
Promise
prevPromise
Таким образом, мы можем загружать наши изображения последовательно, оставаясь асинхронными. Попробуйте этот обновленный Plunkr . Каждый раз, когда вы нажимаете «Получить», постеры будут загружаться последовательно.
function fetchMovies() {
var titles = document.getElementById('titles').value.split(',');
var prevPromise = Promise.resolve(); // initial Promise always resolves
titles.forEach(function(title) { // loop through each title
prevPromise = prevPromise.then(function() { // prevPromise changes in each iteration
return getMovie(title); // return a new Promise
}).then(function(data) {
var img = JSON.parse(data)[0].poster.imdb;
document.getElementById('movies').innerHTML = document.getElementById('movies').innerHTML + '<img src="' + img + '"/>';
}).catch(function(error) {
console.log(error);
});
});
}
Вариант 2
В следующем коде Promise.all()
Promises
Promises
Ценность выполнения этого Promise
Promise
Итак, как только Promise
Вот Plunkr для этого. Также обратите внимание, что в случае Promise.all()
Promise
Promise
function fetchMovies() {
var titles = document.getElementById('titles').value.split(',');
var promises = [];
for (var i in titles) {
promises.push(getMovie(titles[i])); // push the Promises to our array
}
Promise.all(promises).then(function(dataArr) {
dataArr.forEach(function(data) {
var img = JSON.parse(data)[0].poster.imdb;
document.getElementById('movies').innerHTML = document.getElementById('movies').innerHTML + '<img src="' + img + '"/>';
});
}).catch(function(err) {
console.log(err);
});
}
Вывод
В этой статье обсуждались некоторые из более сложных концепций обещаний JavaScript. Просто убедитесь, что вы обновили свой браузер до Chrome 32 beta или последней версии Firefox, чтобы запустить эти примеры кода. Браузерам потребуется некоторое время, чтобы полностью реализовать эту функцию. Кроме того, обещания, безусловно, следующая большая вещь в JavaScript.