В последнее время я делаю кучу вызовов API в JavaScript, что обязательно означает, что я играю с промисами, чтобы уберечь себя от «ада обратных вызовов». Определенно были времена, когда я обнаруживал, что отлаживаю условия гонки, не зная об этом, или у меня был какой-то сильно искаженный вправо код из-за вложения всех моих обратных вызовов, когда я впервые познакомился с JavaScript, поэтому я собираюсь предложить некоторую помощь тем из вас. кто может быть в одной лодке.

Для целей этого поста я собираюсь продемонстрировать Star Wars API (да, Интернет — удивительное место) и выполнять HTTP-вызовы с использованием NPM-пакета request.

Допустим, я хочу найти рейтинг гипердвигателя звездолета, который чаще всего появляется в первом фильме «Звездные войны» Новая надежда. Это двухэтапный процесс: сначала мне нужно выяснить, какой звездолет чаще всего появляется в первом фильме «Звездные войны», а затем мне нужно выяснить рейтинг гипердвигателя этого звездолета. Давайте рассмотрим некоторые из способов, которыми можно подойти к этому:

Плохо

Первый шаг к решению этой проблемы может состоять в том, чтобы сделать два вызова API полностью раздельно, один за другим. Я определенно попал в эту ловушку, когда впервые изучал язык, и это действительно запутанное положение.

var starshipURL;
request('https://swapi.co/api/films/1', (error, response) => {
  starshipURL = JSON.parse(response.body).starships[0];
});
request(starshipURL, (error, response, body) => {
  var rating = JSON.parse(body).hyperdrive_rating;
  console.log(rating);
});

Это почти наверняка приведет к ошибке. Почему? Это потому, что мы ввели в наш код состояние гонки — программа продолжает выполняться после вызова API из-за однопоточной природы JavaScript. Он не будет ждать, пока будет определено starshipURL, прежде чем сделать второй запрос.

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

Лучшее

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

request('https://swapi.co/api/films/1', function(error, response) {
  starshipURL = JSON.parse(response.body).starships[0];
  request(starshipURL, function(error, response, body) {
    var rating = JSON.parse(body).hyperdrive_rating;
    console.log(rating);
  });
});

Это работает! Когда я запускаю его, он регистрирует «2.0», что, по-видимому, является рейтингом гипердвигателя звездолета, который я ищу. Я точно не знаю, что это означает в терминах «Звездных войн» или кто выбрал это число, но, по крайней мере, мой код работает. Так в чем проблема? Для таких небольших проблем, как эта, когда вы делаете всего несколько простых запросов, вы можете избежать вложения обратных вызовов друг в друга. Но что, если бы я захотел взять рейтинг гипердвигателя этого звездолета и сохранить его в базе данных или сделать с ним еще один вызов API? Вы можете начать визуализировать, насколько вложенным станет этот код, и в конечном итоге я буду писать в дальней правой части экрана. Хотя это функционально, это не долгосрочное решение. Введите обещания.

Лучший

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

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

new Promise(function(resolve, reject) {
  request('https://swapi.co/api/films/1', function(error, response){
      if (error) {
        reject(error);
      } else {
        var film = JSON.parse(response.body);
        resolve(film);
      }
  })
})
.then(function(film) {
  var starshipURL = film.starships[0];
  return new Promise(function(resolve, reject) {
    request(starshipURL, function(error, response) {
      if (error) {
        reject(error);
      } else {
        rating = JSON.parse(response.body).hyperdrive_rating;
        resolve(rating);
      }
    });
  });
})
.then(function(rating) {
  console.log(rating);
})
.catch(function(error) {
  console.log(error);
});

Как видите, функция внутри .then() вызывается только тогда, когда промис выполнен, а это значит, что ваш код гарантированно никогда не будет выполняться в неожиданном для вас порядке. Кроме того, я мог бы сделать еще сотню вызовов API, и мне никогда не пришлось бы вкладывать ни один из них, что сохраняет выравнивание моего кода. Тем не менее, это ни в коем случае не идеально. Единственная явная проблема с этим решением — его многословие. То, что я мог бы сделать в 7 строк путем вложения, теперь мне приходится делать в 28 строках, что усложняет просмотр и немедленное понимание. К счастью, многие пакеты в JavaScript автоматически возвращают промисы, так что вам не нужно создавать их самостоятельно. Например, axios — это HTTP-клиент на основе промисов, который мы могли бы легко использовать для выполнения этих вызовов API, но это было бы не так весело, как копаться в мельчайших подробностях промисов самостоятельно.

Так вот оно у тебя! Надеюсь, вы кое-чему научились, хотя бы потому, что рейтинг гипердвигателя звездолета, который чаще всего появляется в Новой надежде, равен 2,0. Если вы такой же большой поклонник Promises, как и я, или хотите узнать обо мне больше, вы можете посетить мой веб-сайт по адресу www.benhubsch.me.