Статьи

Более глубокое погружение в обещания JavaScript

В моей предыдущей статье о новом 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() Значение, возвращаемое обратным вызовом, используется для выполнения или отклонения обещания. Но если обратный вызов возвращает другое PromisePromisethen()Promise

Мы также приковали цепочку к другому then()PromisePromise Вы можете продолжать цепочку Promises Если вам нужно обработать любые исключения, вы можете добавить catch()

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

Методы resolve()reject()

API Promise предоставляет несколько полезных методов, облегчающих нашу жизнь. Одним из них является resolve()Promise Это означает, что если вы создадите Promisethen() Вы также можете передать аргумент метода 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 введите названия фильмов, разделенные запятыми, и нажмите кнопку «Получить». Если вы нажмете «Получить» несколько раз, вы поймете, что нет определенного порядка загрузки изображений! Обещания могут быть выполнены в любом порядке, и поэтому наши изображения также каждый раз приходят в другом порядке. Таким образом, этот код не будет служить нашей цели, если нам нужно выбрать постеры фильма в определенном порядке.

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

Опция 1

Посмотрите на следующий фрагмент. Мы начинаем с создания Promise Это используется для отслеживания предыдущего Promise Внутри цикла мы вызываем prevPromise.then()PromiseprevPromise Это PromisePromisegetMovie(title) Таким образом, PromisePromiseprevPromise Таким образом, мы можем загружать наши изображения последовательно, оставаясь асинхронными. Попробуйте этот обновленный 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()PromisesPromises Ценность выполнения этого PromisePromise Итак, как только Promise Вот Plunkr для этого. Также обратите внимание, что в случае Promise.all()PromisePromise

 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.