Код внутри цикла while не выполняется JavaScript - PullRequest
1 голос
/ 06 июля 2019

Я работаю с этим циклом while, и он не работает. Я решил использовать отладчик Google Chrome и увидел, что код внутри не выполняется.

Все время он проверяет условие, начинает первую строку кода внутри и снова возвращается, чтобы проверить условие.

Это сервер NodeJS, и я использую API Spotify.

app.get('/process', ensureAuthenticated, function (req, res) {
  let init_array = text.split(" ");
  let modtext = init_array;

  while (init_array.length != 0) {
    spotifyApi.searchTracks(modtext.join(" "))
      .then(function (data) {
        try {
          console.log(data.body.tracks.items[0].name);
          for (let i = 0; i < modtext.length; i++) {
            init_array.shift();
          }
          modtext = init_array;
        } catch (err) {
          console.log("No song");
          modtext.pop();
        }
      });
  }


  res.redirect('/');
});

Ответы [ 3 ]

2 голосов
/ 07 июля 2019

Этот вопрос лучше всего понять, если понять, как node.js использует цикл обработки событий. По своей сути, node.js запускает ваш Javascript в одном потоке и использует цикл событий, чтобы управлять завершением событий вне этого единственного потока, таких как таймеры, сетевые операции, операции с файлами и т. Д. *

Давайте сначала начнем с очень простого while() цикла:

let done = false;

setTimeout(() => {
    done = true;
}, 100);

while(!done) {
     // do whatever you want here
}
console.log("done with while loop");    // never gets called

На первый взгляд, вы можете подумать, что цикл while будет работать в течение 100 мс, а затем done будет установлен в значение true и цикл while завершится. Это не то, что происходит. На самом деле это бесконечный цикл while. Он запускается, работает и работает, а переменная done никогда не устанавливается на true. console.log() в конце никогда не запускается.

У него есть эта проблема, потому что setTimeout() является асинхронной операцией, и она сообщает о своем завершении через цикл обработки событий. Но, как мы описали выше, node.js запускает свой Javascript как однопоточный и получает следующее событие из цикла событий только тогда, когда этот единственный поток завершает свою работу. Но while не может завершить то, что он делает, пока done не будет установлен в true, а done не может быть установлен в true, пока цикл while не завершится. Это противостояние, а цикл while работает вечно.

Итак, в двух словах, пока выполняется какой-либо цикл, НИКАКАЯ асинхронная операция никогда не обработает свой результат (если только он не использует await внутри цикла, который является чем-то другим). Асинхронные операции (или все, что использует цикл событий) должны ждать, пока не завершится текущий работающий Javascript, и затем интерпретатор может вернуться к циклу событий.


Ваш цикл while имеет ту же проблему. spotifyApi.searchTracks() - это асинхронная операция, которая возвращает обещание, и все обещания сообщают свои результаты через очередь событий. Итак, у вас такое же противостояние. Ваш обработчик .then() не может быть вызван, пока не завершится цикл while, но ваш цикл while не может завершиться, пока не будет вызван обработчик .then(). Ваш while цикл будет просто бесконечно повторяться до тех пор, пока вы не исчерпаете какой-то системный ресурс, и ваши обработчики .then() никогда не получат шанс выполнить.


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

У вас, кажется, есть N поисков, и вы регистрируете что-то в каждом поиске. Вы можете сделать их все параллельно и просто использовать Promise.all(), чтобы отслеживать, когда они все сделаны (без while петли вообще). Или вы можете упорядочить их, чтобы запустить один, получить его результат, а затем запустить другой. Ваш вопрос не дает нам достаточно информации, чтобы знать, какой вариант будет наилучшим.

Вот одно из возможных решений:

Последовательность операций с использованием async / await

Здесь обработчик запроса объявлен async, поэтому мы можем использовать await внутри цикла while. Это приостановит цикл while и позволит обрабатывать другие события в ожидании разрешения обещания.

app.get('/process', ensureAuthenticated, async function (req, res) {
  let init_array = text.split(" ");
  let modtext = init_array;

  while (init_array.length != 0) {
    try {
      let data = await spotifyApi.searchTracks(modtext.join(" "));
      console.log(data.body.tracks.items[0].name);
      for (let i = 0; i < modtext.length; i++) {
        init_array.shift();
      }
      modtext = init_array;
    } catch (err) {
      console.log("No song");
      modtext.pop();
    }
  }
  res.redirect('/');
});
1 голос
/ 06 июля 2019

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

В этом случае spotifyApi.searchTracks() возвращает Promise. Как только Spotify API завершит поиск , будет запущена функция в then().

0 голосов
/ 06 июля 2019

Давайте используем async / await для решения этой проблемы, я понятия не имею, какие данные вы получаете в text, но я думаю, что это хороший пример для понимания концепции асинхронной обработки.

app.get('/process', ensureAuthenticated, async function (req, res, next) {
  try {
    const modtext = text.split(" ");
    const promises = modtext.map(item => spotify.searchTracks(item));
    const response = await Promise.all(promises);

    response.forEach(data => {
       // if you use lodash, simply use a get function `get(data, 'body.tracks.items[0].name')` => no need to check existence of inner attributes
       // or use something like this
       if (data && data.body && data.body.track && data.body.tracks.items && data.body.tracks.items[0]) {
          console.log(data.body.tracks.items[0].name);
       } else { 
          console.log('No song');
       }
    });

    res.redirect('/');
  } catch(err) {
     next(err)
  }
});

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

...