Обнаружение конца цикла внутри асинхронного кода, который находится внутри другой функции внутри этого цикла

1

Итак, у меня есть этот код:

  for (let i = 0; i < array.length; i++) {
    let url = array[i];
    YTDL.getInfo(url, function(err, info) {
      if (err) {
        message.channel.send("There was an error while checking information about a video, try again soon.");
        throw err;
      }
      songEmbed.addField("Title:", info.title);
      if (i == array.length - 1) message.channel.send(songEmbed);
    });
  }

И проблема в том, что функция внутри YTDL.getInfo() вызывается после завершения цикла for, но мне нужно вызывать message.channel.send(songEmbed) только после последней итерации (см. Код), я старался изо всех сил, чтобы Решите это самостоятельно, например, с помощью array.forEach(), но я не могу понять, как передать правильный индекс элемента в эту функцию внутри YTDL.getInfo(). Надеюсь, вы меня понимаете.

  • 0
    Я подозреваю, что getInfo работает асинхронно.
  • 0
    @kemicofa Потому что все обратные вызовы YTDL.getInfo (), которые добавляют поле в songEmbed, еще не были вызваны.
Теги:

5 ответов

0
Лучший ответ

Согласно примерам npm, getinfo возвращает обещание. Решение с Promise.all, Array # map и Array # forEach

Promise.all гарантирует, что обратный вызов then будет вызван после завершения каждого запроса.

values представляет array info о каждом запросе.

Promise.all(array.map(url => YTDL.getInfo(url).catch(e => e))
.then(results=>{
  //all the requests have ended
  //add all titles to songEmbed
  results.forEach(result=>{
     if(result instanceof Error){
        console.warn(result.message);
     } else {
       songEmbed.addField("title:", result.title)
     }
  });
  //send songEmbed
  message.channel.send(songEmbed)
}).catch(err=>console.warn(err.message));
  • 0
    Можно ли поймать ошибку, используя Promise, как это? Например, если одно из видео недоступно.
  • 0
    @AnB Да, конечно. Просто добавьте catch в конце. Тем не менее, это все или ничего. Это означает, что в случае сбоя одного из запросов все завершается неудачей. Кроме того, вы можете проверить этот вопрос, который, вероятно, то, что вы ищете: stackoverflow.com/questions/30362733/… . Я также добавлю решение к моему ответу через мгновение.
Показать ещё 2 комментария
0

Поскольку код выполняется асинхронно, цикл завершается до завершения функции.

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

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

function magic_func(array, callback) {
      for (let i = 0; i < array.length; i++) {
        let url = array[i];
        YTDL.getInfo(url, function(err, info) {
          if (err) {
            message.channel.send("There was an error while checking information about a video, try again soon.");
            throw err;
          }
          songEmbed.addField("Title:", info.title);
          if (i == array.length - 1) {
            message.channel.send(songEmbed)
            callback()
          }
        });
      }
}

Когда обратный вызов вызван, вы можете продолжить делать следующий шаг, например:

magic_func(array, function() {
  //ADD your next code here
})
0

То, что вы испытываете, является одной из самых неприятных/мощных вещей с javascript, асинхронными методами. Я предполагаю, что в методе getInfo вы делаете какую-то форму вызова ajax. Javascript, чтобы сэкономить время и ресурсы, позволит перевести вызов в фоновый режим, чтобы он не блокировал выполнение следующего кода. Обычно это хорошо, так как позволяет одновременно запускать большое количество javascript, и страница загружается намного быстрее, чем если бы она была синхронной. Но для вас это означает, что при доступе к info.title он фактически не был установлен. Простой и грязный способ исправить это - добавить вызов setTimeout вокруг материала, который вы хотите вызывать в цикле for. Что-то вроде:

setTimeout(function(){ 
      if (err) {
        message.channel.send("There was an error while checking information about a video, try again soon.");
        throw err;
      }
      songEmbed.addField("Title:", info.title);
      if (i == array.length - 1) message.channel.send(songEmbed); 
}, 3000);

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

0

Требуется время, чтобы получить ответ от одного URL, не говоря уже о нескольких URL в вашем массиве. Это асинхронный вызов, поэтому для ожидания ответа следует использовать Promises. Существует множество примеров. Это должно быть полезно https://medium.freecodecamp.org/javascript-from-callbacks-to-async-await-1cc090ddad99

0

Попробуйте понять основы синхронизации/асинхронности в JavaScript. Для этого есть самые популярные ресурсы:

https://www.youtube.com/watch?v=cCOL7MC4Pl0

https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/

https://www.youtube.com/watch?v=8aGhZQkoFbQ

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


А как насчет вашего вопроса и решения для него просто используйте async для цикла, ссылки для ознакомления:

https://hackernoon.com/async-await-essentials-for-production-loops-control-flows-limits-23eb40f171bd

https://codeburst.io/asynchronous-code-inside-an-array-loop-c5d704006c99

Ещё вопросы

Сообщество Overcoder
Наверх
Меню