# Alot - turns your arrays into lazy and async streams

Here I will go through the 📦 [alot 🔗](https://github.com/atmajs/alot) library, which I use to work with arrays in JavaScript/TypeScript. Though there are already dozens of utility libraries for arrays, there was always something I've missed there. 

To position the `alot` library on a "map" and give you the orientation - I would say, it is somewhere between JavaScript native array methods and [`Rx`](https://rxjs.dev/), and in no way completely replaces both of them. `Alot` is highly inspired by **`LINQ`** methods from `.NET`.


### TypeScript and installation

The library is implemented in TypeScript and has complete typings support out of the box, but can be used also in the plain js environment.

```ts
$ npm i alot
```

> If you, the reader, still do not use TS on your daily dev basis, I would highly encourage you to switch to typescript. In later articles, I will show how I use TypeScript with no configuration and compilation hassle, and use it as it were a JavaScript (_as it actually is, but with sweet additions._)


### 🌱 Laziness 

Similar to `rx` cold observables,  the chained methods won't be evaluated until you call `subscribe` method - the equivalents in `alot` library: `toArray`, `toArrayAsync`, `first`, `firstAsync` etc. But let's compare these two examples:

```js
import alot from 'alot'

let arr1 = users
    .filter(user => user.score > 10)
    .map(user => user.name.toUpperCase())
    .slice(0, 5);

let arr2 = alot(users)
    .filter(user => user.score > 10)
    .map(user => user.name.toUpperCase())
    .take(5)
    .toArray()
```

Though the code here does the same things and looks similar, the **flow** is **different**. JavaScript methods do the following:

1. `filter` all users by some `score` field
2. `map` all previously filtered users to their uppercased names and create a new array of strings
3. `take`(slice) the first `5` usernames.

As you may already have noticed, the caveat here is when the `users` array has lots of items - the engine goes through all the users to create a new filtered array, then goes over each item to pick the username and create the new array with names.

The example with `alot` library has the **reversed direction flow**. When the js engine evaluates `filter`, `map`, and `take` methods, it builds the query that gets executed when we call the `toArray()` method and the flow is:

1. `take` 5 elements from the underlying `map` stream
2. `map` users to their uppercased names from the underlying `filter` stream. As only 5 elements were requested it takes and maps also only 5 elements
3. `filter` users by some `score` field. As only 5 elements were requested, it processes filtering only until 5 elements are matched

> When I'm writing code I still think in forward-flow `filter>map>take`, but understanding how laziness works under the hood is beneficial.

So, in the best case, if the first 5 users have a score greater than 10, then the js engine visits only those 5 elements for filtering and mapping.

I have to mention, that it works great for this example. If we wouldn't have `slice/take` methods, then in `alot` example we would obviously `map` **all** filtered users and we wouldn't benefit from the laziness nature of `alot` lib. That's why I still often use js native array methods, and the `alot` API allows me quickly switch between different flows.

The laziness brings us to another feature, which was easy to implement - the asynchronous.


### ⛓️ Asynchronous

All methods in `alot` library have also there asynchronous variations, which accept async methods. 
From the previous example, what if we would want to load also user's comments along with uppercased username - for native js methods we have a huge headache, how to accomplish this. I have seen such **wrong** ❌ solution:

```ts
let promises = users
    .filter(user => user.score > 10)
    .map(async user => {
        return {
            username: user.name.toUpperCase(),
            comments: await Api.loadComments(user.id)
        };
    })
    .slice(0, 5);
let arr1 = await Promise.all(promises);
```

Wrong things about this could be:

- here we load comments for **all filtered** users, even though we need only 5.
- even, if we would need all users, not only 5, we make anyway all requests parallel - which means if we have an array with `1000` elements - we make `1000` requests simultaneously. (_Though browsers and nodejs have max number for open connections, not all requests go immediately to the server, but it could cause timeout errors anyway_)

With `alot` library it would be almost similar and ✅

```ts
let arr2 = await alot(users)
    .filter(user => user.score > 10)
    .mapAsync(async user => {
        return {
            username: user.name.toUpperCase(),
            comments: await Api.loadComments(user.id)
        };
    })
    .takeAsync(5)
    .toArrayAsync({ threads: 2})
```

And the previous issues are solved:

- due to the `take` method, we `map` only 5 elements, which means we make only 5 api requests to load the comments.
- `alot` library can handle the **async queue** - in this example, we make `2` requests to the server at once (_this number is just an example, you can set any number of simultaneous async tasks, default is `4`_)

So with little code change, we managed to create a **lazy asynchronous stream**

We can also make our `filter` async, in case we have to get the data for filtering also async.

```ts
let arr2 = await alot(users)
    .filterAsync(user => Api.isActive(user))
    .mapAsync(async user => {
        return {
            username: user.name.toUpperCase(),
            comments: await Api.loadComments(user.id)
        };
    })
    .takeAsync(5)
    .toArrayAsync({ threads: 2 })
```

> Аttentive reader could notice, that such data loading is not optimal. Batch load by providing the array of userIds to the backend would be better. This example just demonstrates the async tasks. This is just a tool, how and when to use it depends on you.


### ⚙️ Additional array methods.

There are some other convenient methods to work with arrays. Check the [Documentation](https://github.com/atmajs/alot)

The methods I use often are:

- `groupBy(user => user.score)`
    Returns stream of groups (`{ key, values: T[] }[]`)  grouped by the same value, by the score in this example.

- `distinctBy(user => user.city)`
    Returns stream of unique items by the field value. In this example: a user per city.

- `sortBy(user => user.age, 'asc')`
    Returns sorted stream. Possible directions `asc` and `desc`. For better text sorting there is `sortByLocalCompare` method.

- `toMap(user => user.id, user => user.name)`
    Returns an `Map`. From the example, the keys would be the ID of a user, and the value would be the name. 
    
    I use this also to improve performance. Imagine, you need to query the name of a user by id often. Then instead of `users.find(x =>x.id === id)?.name` I create a `Map` of `id:name` and then simply get the name: `map.get(id)` 
    
    _`toDictionary` method returns `object` instead of `Map`, just in case it is more convinient for you_

--- 

*Happy coding*

🏁
