Async loops, and why they fail! Part 3
Yet more problems with async calls and loops in JavaScript: how to handle testing for some and every.
As we saw in the previous articles in this series (Part 1, Part 2) JavaScript’s standard higher order functions such as map()
or forEach()
do not work well if async functions are passed to them. In this article we’ll continue our study of possible fixes, by finding alternative implementations for some()
and every()
.
When looking at
some()
andevery()
I thought they would probably fail (because many other functions also did fail!) just because they weren’t promise-ready, but the reason here was simpler: if we pass an async function to them, a promise is returned every time — and since promises are “truthy” objects, all values were understood to pass the test, no matter what the async call returned!
It should be said that at first I thought implementing these functions would be trivial: to check if some element of the array fulfills a condition, why not use mapAsync()
first (our async-aware version of map()
) and then apply some()
to its output? (Of course, if I wanted to check if every element fulfilled a condition, after mapAsync()
I would have used every()
.) Fortunately, I didn’t go that way , because some()
is supposed to stop looping as soon as it finds a value that fulfills the condition (and every()
stops when it finds an element that doesn’t fulfill the condition) so a single async call could be enough in some cases, while my plan would always involve calls for all elements; not efficient at all!
Implementing some()
Given our experience with looping and reducing (see part 1 of this series) implementing someAsync()
was not hard.
We loop through the array, and the important thing is stopping doing async calls as soon as we know for sure that some elements fulfills the given predicate — a simple thing to achieve by using the||
operator. If an async call fails, we just keep passing the initial false value.
As in the two previous articles, we’ll always be coding functions in two ways: as a method added to the Array.prototype (even if this practice is not usually recommended…) and also as a single, stand-alone function; pick whichever you prefer.
We can test it out very simply; let’s suppose we want to check if some value is greater than 4 — and, as in other examples in this series of articles, let’s fake some failures if the async call gets 2 as its argument.
Output is as follows; as soon as one element in the array is seen to be greater than 4 (the condition we were testing for) looping stops.
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
We now know how to implement an async-aware version of some()
; what about doing the same for every()
?
Implementing every()
For a while, I thought that implementing every()
would be trivial. Given that every()
element of the array satisfies a given predicate if and only if there aren’t some()
elements that do not satisfy it, I planned on simply basing my implementation on negating the predicate and using some()
. However, that idea doesn’t work if some async calls do fail — for instance, if all calls failed, then some()
would report that no elements don’t satisfy the given condition — but that really doesn’t mean that every element satisfies it! In any case, just a couple of small changes to someAsync()
were enough to provide the new everyAsync()
function I wanted.
Compare the code with someAsync()
— the main differences is that we start reducing with a true
value instead of false
, and we use &&
instead of ||
; simple!
If we test it with failures, as shown above, we get a quite quick exit from the loop.
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
If we don’t simulate any failures (remove v==2
in the call to getAsyncData()
) then we correctly see the loop going to the end of the array.
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
Success! Our everyAsync()
implementation performs the same way as every()
would, but correctly handling promises and async calls.
Summary
In this article, we provided alternative implementations for some()
and every()
. In the next article in the series, we’ll be providing alternatives for find()
and findIndex()
, which do also fail to work properly.
References
This article is partially based on Chapter 6, “Programming Declaratively — A Better Style” of my “Mastering JavaScript Functional Programming” book, for Packt; logical functions weren’t covered there.
Check MDN for the description for array.some() and array.every().
For the relationship between some()
and every()
this article on logic quantifiers can help, but remember we didn’t use this for our coding.
Code for all articles in the series is available at my repository:
All the other articles in this series: