Sunday 9 May 2021

Playing with ixjs AsyncIterable

Some days ago I came across this beautiful library that by means of an ix.Iterable and ix.AsyncIterable classes sort of brings the functionality available for push streams in rxjs to pull streams (both synchronous and asynchronous iterables and generators). They talk about the "arrays extras" methods, but there are more. It's related to what I prototyped time ago here and here.

The usage pattern is based on rxjs, so once you have created an (ix)Iterable/AsyncIterable object from a generator/iterable, you use the pipe function to chain functions. This ends up creating a chain of objects (FiterIterable, MapIterable...) working in a lazy way. Once you've created the chain, nothing will happen until you start to iterate the resulting Iterable, and the functions in the chain are not executed for all the items at the same time when the iteration starts, but just one by one the moment you are retriving that specific element. Well, this should sound very familiar if you're used to working with Linq in the .Net world.

The samples in their web give you a basic introduction. For AsyncIterables, one of the most useful use case is related to what they explain in the Converting from Iterable to AsyncIterable section, but the sample there is too trivial to get the full idea I think. Imagine we have an array and we want to apply some mapping and filtering, but some of the iteratee functions (lodash/fp parlor, the function that we pass to map-filter) are asynchronous and others synchrous. For that you'll need the methods in the ix.AsyncIterable, that work both with async and sync functions. Let's see an example (given an array of cities, call and async api to get its countries, filter the non null results, and then convert to upper case)


import { from as asyncIterableFrom } from 'ix/asynciterable/index.js';
import { filter as asyncFilter, map as asyncMap } from 'ix/asynciterable/operators/index.js';

function asyncSleep(interval){
    return new Promise(resolveFn => setTimeout(resolveFn, interval));
}

async function findCountryForCityAsync(city){
    let countries = [
        {
            name: "France",
            mainCities: ["Toulouse", "Paris", "Lyon", "Marseille", "Bordeaux"]
        },
        {
            name: "Belgium",
            mainCities: ["Bruxelles", "Antwerp", "Liege"]
        }
    ];
    console.log("finding country for " + city);
    await asyncSleep(2000);
    let country = countries.find(country => country.mainCities.findIndex(_city => _city == city) > -1);
    return country ? country.name : null;
}

let cities = ["Toulouse", "Paris", "Ghent", "Bruxelles"];

let transformedCitiesIterable = asyncIterableFrom(cities).pipe(
    asyncMap(findCountryForCityAsync),
    asyncFilter(city => city !== null),
    asyncMap(x => x.toLocaleUpperCase())
);
console.log("transformedCitiesIterable type: " + transformedCitiesIterable.constructor.name);
//we have an objects chain like this:
// MapAsyncIterable -> FilterAsyncIterable -> MapAsyncIterable -> FromAsyncIterable

//async main
(async () => {
    console.log("starting asyncIterableTest");
    for await (let city of transformedCitiesIterable){
        console.log(city);
    }
    console.log("ending asyncIterableTest");
})();


I've uploaded it to a gist. The documentation for this nice library is mainly missing, but someone generated docs for it here

The .Net equivalent (more or less) to this library is System.Linq.Async, and you can find a good sample here.

No comments:

Post a Comment