Asynchronous enumerations

Non-blocking iteration over large data sets

Many modern languages support asynchronous iterations. Say you want to iterate over 5000 entities from the API. But that will likely take very long time to execute the query, serialize server side, deserialize client side etc. So, instead of trying to fetch the whole thing at once, you may want to fetch it in pages. With the use of async iterators, this is seamless for the code outside. JavaScript has for await ... of, .NET has IAsyncEnumerable, Python has asynchronous generators.

Consumer of the async enumeration in C# is as simple as this:

await foreach (var component in api.GetComponents(tenant.Id, tenant.Region,
    filter: $"{nameof(Component.AssetModelId)} eq {modelId}"))
{
    Console.WriteLine($"Component {component.EntityId}: {component.Name}");
}

All Flex API endpoints returning lists of data support OData $top and $skip query parameters which can be used for efficient paging. Following is an example of async enumerable implementation in C#.

public static async IAsyncEnumerable<Component> GetComponents(this IFlexAPI api, 
    Guid tenantId, Region tenantRegion,
    [EnumeratorCancellation] CancellationToken cancellationToken = default,
    string expand = null,
    string select = null,
    string filter = null,
    string orderBy = null)
{
    const int batchCount = 200;

    // get overall count with the first batch
    var batch = await api.Components_GetAsync(tenantId.ToString(), tenantRegion, expand: expand,
        filter: filter, select: select, orderby: orderBy, count: true, 
        cancellationToken: cancellationToken,
        top: batchCount, skip: 0).ConfigureAwait(false);

    if (batch.OdataCount == 0)
    {
        yield break;
    }
    else
    {
        foreach (var item in batch.Value)
        {
            cancellationToken.ThrowIfCancellationRequested();
            yield return item;
        }
    }

    var iterations = batch.OdataCount / batchCount;
    if (batch.OdataCount % batchCount > 0)  iterations++; 

    for (int i = 1; i < iterations; i++)
    {
        batch = await api.Components_GetAsync(tenantId.ToString(), tenantRegion, expand: expand,
            filter: filter, select: select, orderby: orderBy, count: false, 
            cancellationToken: cancellationToken,
            top: batchCount, skip: batchCount * i).ConfigureAwait(false);

        foreach (var item in batch.Value)
        {
            cancellationToken.ThrowIfCancellationRequested();
            yield return item;
        }
    }
}