Async loops, and why they fail! Part 4

A couple more problems with async calls and loops; how to handle searching.

Federico Kereki
DailyJS

--

In the previous articles in this series (Part 1, Part 2, and Part 3) we looked at problems with functions such as forEach(), map() or every() when used with async functions or promises, and developed some alternative versions that suited our purposes. Let’s finish now by considering a pair of tightly related methods: find() and findIndex() that also need a workaround.

Why do these methods fail? The reason is the same as for some() and every(), which we studied in Part 3 of this series. If we provide them an async function, a promise is returned every time, and promises being “truthy” objects, searches seem to always be successful!

Implementing find()

Let’s remember how array.find() works: basically, it loops through the whole array, looking for a value that satisfies a given function, and when found, looping stops and the value is produced as a result; if the loop gets to the end of the array without success, undefined is returned instead. And yes, this is a bit of a problem is you were looking for an undefined value yourself; how would you know if the search succeeded or not? Using findIndex() would solve that, though.

As in all the previous articles in this series, we’ll always code functions in two ways: as a method to be added to the Array.prototype (even if doing this is usually frowned upon…) and as a common function — you may use any of those.

Our search will have to work with two values: a found boolean attribute that will be true if the search succeeded, and a value attribute that will store the value that satisfied the function. We’ll initialize found to false, and value to undefined; if the search succeeds, we’ll change found to true, and also update value.

Logic is a bit harder — we pass around an object with the found and value attributes, and at the end, we pick out just the latter attribute.

To test this, we could again use our sample array (with values 1, 2, 3, 5, and 8) and see if we can find some value whose square equals 9 — and we’ll also have the async call if provided with 2 as its argument, as in previous examples. Code could be as follows.

If we run this, we get something like the following for both versions of the code.

16:20:37.135 START -- using .findAsync(...) method 
16:20:37.139 Calling - v=1 i=0 a=[1,2,3,5,8]
16:20:38.141 Success - false
16:20:38.142 Calling - v=2 i=1 a=[1,2,3,5,8]
16:20:40.143 Failure - error
16:20:40.143 Calling - v=3 i=2 a=[1,2,3,5,8]
16:20:43.146 Success - true
16:20:43.146 END -- 3

The failure was ignored, and only three async calls were made; after finding that 3 squared equals 9, we got our final result. On the other hand, if we change the test to look for a number whose square is 99, we get an undefined result, after having made async calls for all the values in the array.

16:22:27.521 START -- using .findAsync(...) method 
16:22:27.526 Calling - v=1 i=0 a=[1,2,3,5,8]
16:22:28.528 Success - false
16:22:28.529 Calling - v=2 i=1 a=[1,2,3,5,8]
16:22:30.530 Failure - error
16:22:30.530 Calling - v=3 i=2 a=[1,2,3,5,8]
16:22:33.534 Success - false
16:22:33.534 Calling - v=5 i=3 a=[1,2,3,5,8]
16:22:38.538 Success - false
16:22:38.538 Calling - v=8 i=4 a=[1,2,3,5,8]
16:22:46.544 Success - false
16:22:46.544 END -- undefined

It seems our logic is working fine — and fortunately, implementing the other search method, findIndex(), will now be quite easy.

Implementing findIndex()

After having implemented findAsync() as above, producing an async-aware version of findIndex() is almost trivial — instead of working with a value attribute, we’ll have an index attribute that will be the position in the array of the found value, or -1 if the search failed.

We can test this in the same way we tested findAsync() and all you have to do is a quick “search and replace”, so we don’t really need to show the test code. The same successful search shown above would now produce something like the following.

16:25:00.203 START -- using .findIndexAsync(...) method 
16:25:00.206 Calling - v=1 i=0 a=[1,2,3,5,8]
16:25:01.208 Success - false
16:25:01.209 Calling - v=2 i=1 a=[1,2,3,5,8]
16:25:03.210 Failure - error
16:25:03.210 Calling - v=3 i=2 a=[1,2,3,5,8]
16:25:06.214 Success - true
16:25:06.214 END -- 2

The result is 2, corresponding to the position of value 3 in the array. We can also verify that an unsuccessful search (as shown earlier) will return -1.

16:25:43.583 START -- using .findIndexAsync(...) method 
16:25:43.587 Calling - v=1 i=0 a=[1,2,3,5,8]
16:25:44.589 Success - false
16:25:44.590 Calling - v=2 i=1 a=[1,2,3,5,8]
16:25:46.592 Failure - error
16:25:46.592 Calling - v=3 i=2 a=[1,2,3,5,8]
16:25:49.595 Success - false
16:25:49.595 Calling - v=5 i=3 a=[1,2,3,5,8]
16:25:54.598 Success - false
16:25:54.598 Calling - v=8 i=4 a=[1,2,3,5,8]
16:26:02.606 Success - false
16:26:02.606 END -- -1

So, it seems we got both versions of our searching logic to work — with little extra work!

Summary

In this last article, we finished with the study of dealing with async loops and promises in JavaScript, by developing our own versions of promise-aware code for find() and findIndex().

Even if some of the examples that we saw are probably not too likely, we were able to experiment with several of the latest features of Node.js, and we could also get practice with promises, especially when dealing with failures.

A final comment: do not take all the code we saw as definitive; there certainly are many more ways of doing what I did, and you could get clearer or more performing versions of our functions by going some other way — feel free to experiment, and do comment on your results!

References

This article is also partially based on Chapter 6, “Programming Declaratively — A Better Style” of my “Mastering JavaScript Functional Programming” book, for Packt; searching functions weren’t covered there.

Check MDN for full descriptions of array.find() and array.findIndex().

Code for all articles in the series is available at my repository:

The previous articles in the series are:

--

--

Federico Kereki
DailyJS

Computer Systems Engineer, MSc in Education, Subject Matter Expert at Globant