Еще больше проблем с асинхронными вызовами и циклами в 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()
может помочь эта статья о логических квантификаторах, но помните, что мы не использовали ее для нашего кодирования.
Код для всех статей этой серии доступен в моем репозитории:
Все статьи из этой серии: