Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Js design 1
  • Overview
  • Transcript

3.6 The Iterator Pattern

JavaScript has some iteration built-in—for and for-in loops for example—but to use these, we need to know up-front what type of object we want to iterate. In this lesson, let's use the Iterator pattern to build an asynchronous iterator for any type of object.

3.6 The Iterator Pattern

Hi, folks. In this lesson we're going to take a look at the iterator pattern. JavaScript itself has iteration built into the language. There are a number of array methods that we can use to iterate arrays and we have the for in loop for objects. But the iterator pattern is slightly different in that it doesn't have to happen in a synchronous loop. We choose when to extract the next item from the collection and the iterator itself keeps track of which items have been processed. The purpose of an iterator is to provide access to the items in a collection contiguously while keeping track of which items have been accessed. Usually, an iterator will provide a method called next, which will return the next item in the collection. In some languages, an iterator will provide other methods like hasNext, isDone, first, and reset. Javascript has iterators as a core part of the language in ES7, so iterators are coming to a browser near you at some point in the future. Until this is widely implemented, however, we'll have to create our own. For more information on this, I'd recommend that you take a look at the iterator docs, which are up on MDN. So, let's get started on our own implementation. First of all, let's create a new folder called Iterator. Inside this we can add an iterator module called iterator.js. First of all, we can define a constructor for an interator object. The constructor will be passed a collection of items, so let's store this as an own property within the object. And let's also have a property called index which will keep track of which items have been processed. We can initialize that to 0. It will be useful if our iterator can work seamlessly with either objects or arrays. But we'll handle converting objects passed to the method into arrays in the initialization method. All of our iterator methods will therefore work on the assumption that the collection is an array. So let's add the main methods to the iterators prototype so that all instances of the object will have access to them. The first thing that we need to do is set the constructor property of the object that is going to be used for the prototype back to the iterator constructor. If we don't do this, the constructor will point to the object constructor instead. So now we can start to add the iterator's interface. As the next method is at the core of an iterator, we can add this first. This is all the method needs to do. We've returned the next item in the array using the index proxy of the iterator and we can then increment the index once the item has been returned. We don't need to worry too much about coding defensively here, because if we try to access an array item that doesn't exist it would just return undefined. It shouldn't throw an error or anything. At the same time, it could also be useful to provide an is done method which will return true once all the items in the collection have been traversed. We just return whether the index is equal to the length of the collection. We can also add a reset method which sets the index property of the iterator back to zero just in case we want to traverse the collection again. All we need to do is set the index back to 0 so that the collection can be traversed once again. We can also return the iterator after the collection has been reset as a convenience, so that other methods can be chained on after the reset method. One method I quite like is a method called take, which will return a specified number of items and update the index accordingly. The take method will receive the number of items that we want to take as an argument. First of all, we calculate what the new index will be once we've taken the items and stored this in a variable. We then create the new array from the iterator's collection using the slice method. It's safer to use the slice method from the array prototype just in case the iterator's collection is a string. We pass the collection to the call method as the first argument to set the collection as the this object. We then pass the current index, as the index to start slicing from, and the new index we calculated as the second argument. Lastly, we set the iterator's index to the new index, and then return the new array. So let's stop there. We've got a basic iterator with a simple interface. We can now expose an initialization method from our module so that new instances of the iterator can be easily created. We return an object from our module which has just one method, build. If it looks familiar it's because it is. It's another implementation of the factory pattern that we looked at in a previous lesson. Factories can be very useful. But back to the iterator. Inside our build method we'll first want to initialize some variables. We will only need these if an object is passed to the build method as the collection to base the iterator on. We can use the keys method of the object constructor to extract an array containing all of the keys from the object. So now we can handle some different types that may be passed to the build method. If a string is passed, this will be handled automatically. A string is a collection of characters so it can be naturally iterated. We might want to handle numbers. A number could be seen as a collection of digits after all. So when a number is passed in we just convert it to a string and the iterator will be able to handle it. The other type that may be passed in is an object. An object is a collection of key value pairs, after all. We can extract the values from the object and construct a new array from these. If an object was passed to the build method, the keysArray that we collected at the start of the method will have links. So if this is the case, we can use a simple foreign loop to extract the values from the objects and pass then into our temporary, and then replace the collection with this array. So this is probably all the types that we sensibly want to support. Boolean null and undefined values aren't really suitable for iteration. At this point, we'll have a true array or an array light collection. So, we can try to initialize an iterator. We can check whether the collection has a length and if so we can return a new instance of the iterator. If it doesn't have a length it means it's an unsupported type so we can throw a new error explaining what the problem is. We should be in a position now where we can use our iterator. Let's load up in our init file and then create some iterators. So first of all, let's create an iterator based on an array and invoke the next method whenever we want to process the next item. So, let's now update our main file to load the iterator example files. And let's try running the example in our browser. So we need to actually use the next method to pull items out of our iterator. And as you can see, it is pulling the items from the collection one at a time, each time we invoke the next method. It's not like a loop where it just gets iterated all in one go. So let's try building some other type of iterators. Let's try a string iterator. We can use a loop to process each character in the string if we want. And, let's see if our iterator can handle strings. And it certainly looks like it can. We can also reset the iterator at any time, remember, and use the take method to grab a selection of the items from the collection. So in this lesson we looked at the iterator pattern. It's less common in JavaScript than some of the other patterns that we've looked at in the course, but it can be useful, and it's pretty easy to put together. And as I mentioned earlier, it will become a core part of JavaScript at some point in the future. So, our iterator decouples the processing of a collection of items from the collection of items itself. We don't need to know beforehand whether the collection is an array, an object, a string, or a number. We just use it via the iterator's API and let the iterator take care of it. Thanks for watching.

Back to the top