Hey there!
Some months ago Deno —a kind of successor to Node.js— was released and in the homepage a figured a little demo of how to use it:
import { serve } from "https://deno.land/std@0.69.0/http/server.ts";
const s = serve({ port: 8000 });
console.log("http://localhost:8000/");
for await (const req of s) {
req.respond({ body: "Hello World\n" });
}
Suddenly my eyes went “What is that???” when looking at the await call after the for, but before the (const req of s) on line 4
I had never seen something like that and my first thought was “this is a pretty cool and weird thing that deno does”….
Imagine my surprise when upon reading more about deno I found out that that little piece of code it was in fact valid javascript and it was also valid in Node.js and I had no idea about it
So what is this? why have I never seen it? where should I use it? am I already missing out?
If you have the same questions, then good!
This post will try answering all those things!
First things first:
Iterators
Have you ever seen something like this?
class RandomNumberGenerator {
[Symbol.iterator]() {
return {
next: () => {
return { value: Math.random() };
},
};
}
}
If you did, then great for you, you can skip to the next section!
If you haven’t, then let’s dive a bit into what this is doing:
This class RandomNumberGenerator
is implementing the [Symbol.iterator]
or @@iterator
method (we refer to the method with the double @@
when the method is defined through a Symbol property).
The [Symbol.iterator]
or @@iterator
method when defined on an object allow the object to be iterated!
Since now we defined the @@iterator
method as an instance method of the class RandomNumberGenerator, all the instances of this class will now be iterable. You can now test it by running the below code:
class RandomNumberGenerator {
[Symbol.iterator]() {
return {
next: () => {
return { value: Math.random() };
},
};
}
}
const rand = new RandomNumberGenerator();
for (const random of rand) {
console.log(random);
if (random < 0.1) break;
}
In order for everything to work the @@iterator
method must return an object that contains a next
method AND that next
method needs to return another object with the properties value
and done
.
value
will contain the value returned, while done
will be a boolean that if it’s set to true will end the iteration.
While value
is mandatory, done
can be omitted as in the above example (this allows us to define infinite iterables).
Ok cool!
We can now make things iterable!
When does this come as useful?
I believe it highly depends on the type of business logic that you are creating.
For example the Node.js Design Patterns book —which I highly recommend— gives an example of iterating the elements of a Matrix (which you’ve probably defined as an array of arrays).
Also I believe this article highlights some cool uses. It defines some pretty cool methods that seem very python-esque.
However if you want my honest and personal opinion I’ve yet to encounter a situation in which I say “This is a great use case for iterators”.
In any case, let’s go back to main theme of our article: what else do we need to add the await into that for loop?
Async Iterators
Async iterators are —as the name implies— the async version of what we’ve done in the above example.
Imagine that instead of returning a random number, we instead returned promises. How would that look like?
If we altered the above example to do that, it would look like this:
const simulateDelay = (val, delay) =>
new Promise((resolve) => setTimeout(() => resolve(val), delay));
class RandomNumberGenerator {
[Symbol.asyncIterator]() {
return {
next: async () => {
return simulateDelay({ value: Math.random() }, 200); //return the value after 200ms of delay
},
};
}
}
const rand = new RandomNumberGenerator();
(async () => {
for await (const random of rand) {
console.log(random);
if (random < 0.1) break;
}
})();
What were the changes?
- We first changed the Symbol property to be
asyncIterator
instead of justiterator
- We made the
next
method be an async function. - I created the simulateDelay function which returns a promise that resolves a given value after a given amount of time.
- We added the await in the for loop
- We put the loop inside an Inmediately invoked function expression in order to not have problems with top-level await calls. (Note: this is no longer needed in Node.js version 14+)
And so we’ve made a simple program that is capable of iterating over an object that gets its elements in an async way.
If you know of any other implementation of async iterators outside the small deno example on the homepage please email them to me or comment them below!
Great article! Learned something new. Thanks.
Hey Mike, great article and thanks a million for the mention on Node.js Design Patterns! <3
Hey Luciano!!
Thanks to you for writing the best book on Node.js book I’ve known to date 🙂
Hi Mike,
Thanks for such an elegant and simple explanation. I feel much more confident to tackle the design problem I have in front of me!
BTW, I first encountered this pattern with the zeroMQ library (https://github.com/zeromq/zeromq.js)