Еще больше проблем с асинхронными вызовами и циклами в JavaScript: как проводить тестирование для некоторых и каждого.

Как мы видели в предыдущих статьях этой серии (Часть 1, Часть 2) стандартные функции JavaScript высшего порядка, такие как map() или forEach(), не работают должным образом, если им передаются асинхронные функции. В этой статье мы продолжим изучение возможных исправлений, найдя альтернативные реализации для some() и every().

Когда я смотрел на some() и every(), я подумал, что они, вероятно, потерпят неудачу (потому что многие другие функции также потерпели неудачу!) Только потому, что они не были готовы к обещанию, но причина здесь была проще: если мы передадим async, обещание возвращается каждый раз, а поскольку обещания являются «правдивыми» объектами, все значения считались прошедшими тест, независимо от того, что возвращал асинхронный вызов!

Следует сказать, что сначала я думал, что реализация этих функций будет тривиальной: чтобы проверить, удовлетворяет ли какой-либо элемент массива условию, почему бы не использовать сначала mapAsync() (нашу асинхронную версию map()), а затем применить some() к его выходным данным? (Конечно, если бы я хотел проверить, удовлетворяет ли каждый элемент условию, после mapAsync() я бы использовал every().) К счастью, я не пошел по этому пути, потому что some() должен прекратить цикл, как только найдет значение, которое выполняет условие (и every() останавливается, когда находит элемент, который не удовлетворяет условию), поэтому в некоторых случаях может быть достаточно одного асинхронного вызова, в то время как мой план будет всегда задействовать вызовы для всех элементов; совсем не эффективно!

Реализация некоторых ()

Учитывая наш опыт работы с циклами и сокращением (см. Часть 1 этой серии), реализовать someAsync() было несложно.

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

Как и в двух предыдущих статьях, мы всегда будем кодировать функции двумя способами: как метод, добавленный в Array.prototype (даже если это обычно не рекомендуется…), а также как отдельная автономная функция; выберите то, что вам больше нравится.

Мы можем проверить это очень просто; Предположим, мы хотим проверить, не превышает ли какое-то значение 4 - и, как и в других примерах из этой серии статей, давайте имитируем некоторые сбои, если асинхронный вызов получает 2 в качестве аргумента.

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

13:57:51.045 START -- using .someAsync(...) method 
13:57:51.048 Calling - v=1 i=0 a=[1,2,3,5,8] 
13:57:52.051 Success - false 
13:57:52.051 Calling - v=2 i=1 a=[1,2,3,5,8] 
13:57:54.053 Failure - error 
13:57:54.054 Calling - v=3 i=2 a=[1,2,3,5,8] 
13:57:57.057 Success - false 
13:57:57.057 Calling - v=5 i=3 a=[1,2,3,5,8] 
13:58:02.062 Success - true 
13:58:02.062 END -- true

Теперь мы знаем, как реализовать асинхронную версию some(); как насчет того же для every()?

Реализация every ()

Некоторое время я думал, что реализация every() будет тривиальной задачей. Учитывая, что every() элемент массива удовлетворяет заданному предикату тогда и только тогда, когда нет some() элементов, которые ему не удовлетворяют, я планировал просто основывать свою реализацию на отрицании предиката и использовании some(). Однако эта идея не работает, если некоторые асинхронные вызовы терпят неудачу - например, если все вызовы завершились неудачно, тогда some() сообщит, что ни один элемент не удовлетворяет данному условию - но это на самом деле не означает, что каждый элемент ему удовлетворяет. ! В любом случае, всего пары небольших изменений в someAsync() было достаточно, чтобы реализовать новую функцию everyAsync(), которую я хотел.

Сравните код с someAsync() - основные отличия в том, что мы начинаем сокращать со значения true вместо false и используем && вместо ||; просто!

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

14:06:04.894 START -- using .everyAsync(...) method 
14:06:04.898 Calling - v=1 i=0 a=[1,2,3,5,8] 
14:06:05.901 Success - true 
14:06:05.901 Calling - v=2 i=1 a=[1,2,3,5,8] 
14:06:07.904 Failure - error 
14:06:07.904 END -- false

Если мы не моделируем никаких сбоев (удаляем v==2 в вызове getAsyncData()), то мы правильно видим, что цикл идет до конца массива.

14:07:29.270 START -- using .everyAsync(...) method 
14:07:29.274 Calling - v=1 i=0 a=[1,2,3,5,8] 
14:07:30.274 Success - true 
14:07:30.274 Calling - v=2 i=1 a=[1,2,3,5,8] 
14:07:32.277 Success - true 
14:07:32.277 Calling - v=3 i=2 a=[1,2,3,5,8] 
14:07:35.281 Success - true 
14:07:35.281 Calling - v=5 i=3 a=[1,2,3,5,8] 
14:07:40.287 Success - true 
14:07:40.287 Calling - v=8 i=4 a=[1,2,3,5,8] 
14:07:48.295 Success - true 
14:07:48.296 END -- true

Успех! Наша реализация everyAsync() работает так же, как every(), но корректно обрабатывает обещания и асинхронные вызовы.

Резюме

В этой статье мы предоставили альтернативные реализации для some() и every(). В следующей статье этой серии мы представим альтернативы для find() и findIndex(), которые также не работают должным образом.

использованная литература

Эта статья частично основана на главе 6 Декларативное программирование - лучший стиль моей книги Освоение функционального программирования на JavaScript для Packt; логические функции там не рассматривались.

Найдите в MDN описание array.some () и array.every ().

В отношении взаимосвязи между some() и every() может помочь эта статья о логических квантификаторах, но помните, что мы не использовали ее для нашего кодирования.

Код для всех статей этой серии доступен в моем репозитории:



Все статьи из этой серии: