И 3 разных способа заставить его работать

Посмотрим правде в глаза: с момента появления шаблона async / await мы пытались использовать его повсюду.

Давно прошли и (почти) забыты времена больших цепочек обратных вызовов javascript и Promises.resolve: теперь все функции начинаются с async, даже если он не будет содержать асинхронного кода ... на всякий случай :)

Но некоторые части Javascript еще не готовы к работе с этим шаблоном. Одна из этих частей - .forEach (другие методы, которые не идут вместе с async / await, - это .map, .filter и .reduce, но я напишу об этом другую статью).

.forEach - это метод массива, который позволяет вызывать функцию для каждого элемента массива. Легко, правда?

Итак, это пример .forEach:

Если я сохраню этот скрипт в файле food.js, а затем запустил node food.js, на выходе будет следующий результат:

Итак, все правильно: функция вызывалась для каждого элемента массива, и порядок массива совпадает с порядком вывода console.log. Ура! ✨

Но что произойдет, если функция сложнее, чем отдельный console.log?

Код, который мы написали в предыдущем разделе, великолепен, но я хочу большего. Я хочу знать класс моих любимых блюд (мясо, рыба, овощи, фрукты).

Допустим, что я создал в javascript некоторый сложный алгоритм, который, учитывая во входных данных имени продукта, может возвращать класс. Довольно здорово, а? Это мой лучший код на свете. 🚀

Этот алгоритм использует много вычислительной мощности и ресурсов БД, поэтому он вернет обещание.

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

Чтобы вызвать эту функцию, я буду использовать await внутри моего .forEach. Немного изменим предыдущий код:

Поскольку использование async await в глобальном состоянии еще не является стандартом, я создал функцию run, которая обертывает код. Внутри функции .forEach (теперь это асинхронная функция) мы вызываем AIFoodRecognition с await и печатаем результат.

Легко, правда? Результат должен начинаться с «Старт», затем будут напечатаны все мои любимые классы еды, а затем напечатан «Конец».

Давай попробуем:

Ну… не совсем то, что мы ожидали. Это связано с тем, что .forEach вызывает только функцию, она не ждет ее завершения, и ожидается, что каждая функция AIFoodRecognition завершится не менее чем через 500 мс, что намного больше, чем время, которое основной поток потратит на завершение цикла .forEach и печать End . Это облом!

К счастью, у этой проблемы есть несколько решений.

  • перепишите .forEach для поддержки await / async;
  • использовать for () цикл;
  • используйте Promise.all;

Начнем с наименее удобного, даже если он кажется лучшим:

Перепишите .forEach для поддержки await / async

Кажется, это отличное решение! Давайте найдем код для полифила .forEach, давайте обернем все асинхронной функцией, а затем ... Прибыль! 💰💰💰

Это простой .forEach полифилл:

Давайте изменим его для поддержки async / await:

Как видите, мы добавили async перед функцией в строке 1, а затем перед callback.call мы будем использовать await. Мы собираемся заменить метод forEach в Array.prototype этим методом.

Ok! Теперь попробуем использовать это в нашем предыдущем коде:

И после повторного запуска вывод будет…

ОЙ.

Это потому, что мы забыли кое-что важное: теперь forEach - это асинхронная функция, поэтому, чтобы дождаться его завершения, нам нужно использовать await перед его вызовом.

Если мы просто добавим await перед forEach, как это

И у нас есть правильный результат:

Поэтому нам нужно только добавить await везде, где мы уже использовали .forEach, и обернуть его асинхронной функцией… не стоит того.

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

Давайте попробуем найти другие решения, может быть, менее хакерские.

Используйте наш старый добрый цикл for ()

Месть старого за () цикл. В прошлом мы везде заменяли его новым, более красивым .forEach, а теперь возвращаемся, прося прощения.

Попробуем использовать это в нашем коде:

И… это работает с нашей первой попытки!

Немного лучше: использовать для… из

Одним из вариантов цикла for () является использование шаблона for… of:

Но я думаю, что есть что-то еще лучшее, что, возможно, поможет нам сократить время ожидания.

Используйте Promise.all, чтобы сократить время работы 🕐

До сих пор, чтобы завершить нашу программу, мы ждали 3 секунды (500 мс для каждого элемента в foodArray). Это потому, что ожидание было внутри цикла. Но что, если бы у нас было 100 элементов в foodArray? Или 1000? Время ожидания было бы ужасным.

Может быть, есть способ сделать все распознавание еды параллельно, вместо того, чтобы ждать завершения каждого, прежде чем начинать следующий? Да, есть: он называется Promise.all. Этот метод Promise принимает массив в качестве параметра и будет возвращать только тогда, когда все элементы внутри массива завершены, возвращая новый массив результатов.

Мы собираемся немного изменить код, но я обещаю, что оно того стоит:

Итак, теперь у нас есть новый массив с именем promises, и внутри .forEach мы собираемся добавить каждый вызов функции AIFoodRecognition, таким образом мы запускаем каждое распознавание параллельно. Помните: AIFoodRecognition возвращает Promise, поэтому мы по-прежнему не можем получить наши классы из этого массива. Для этого нам нужно использовать Promise.all с единственным ожиданием, которое вернет новый массив, содержащий возвращаемые значения каждого элемента нашего массива обещаний. Теперь нам нужно только циклически перебрать этот массив, чтобы распечатать результаты, и все готово! ✨

Давайте посмотрим на разницу между кодом с Promise.all и с циклом for () (я установил задержку 1500 мс вместо 500 мс для этих гифок)

Как видите, метод Promise.all намного быстрее, чем цикл for (). Поэтому, если вам не нужно что-то из выходного результата предыдущего цикла, вы всегда должны использовать версию Promise.all для запуска некоторого кода async / await в цикле.

Дайте мне знать, что вы думаете, или если у вас есть другие способы использовать ожидание внутри цикла, вы всегда можете найти меня в Twitter 💛