Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle multiple awaits in async function

I have multiple API calls to be made which fetch via an API, write data to DB via API, send output to front end via another API.

I have written async function with await like below.

The first two should run one after another but the third one can run independently and doesn't need to wait for the first two fetch statements to complete.

let getToken= await fetch(url_for_getToken);
let getTokenData = await getToken.json();

let writeToDB = await fetch(url_for_writeToDB);
let writeToDBData = await writeToDB.json();

let frontEnd = await fetch(url_for_frontEnd);
let frontEndData = await frontEnd.json();

What is the best way to handle such multiple fetch statements ?

like image 551
Yasar Abdullah Avatar asked Nov 12 '19 09:11

Yasar Abdullah


People also ask

Can we have multiple await in async method C#?

Wait() (careful you don't block the UI thread) or even do an await on it if it itself is an async method. If it is not the first await in the async method it will just return to whichever handler in whichever thread was executing the "continuation" of the last Task that was awaited.

Do async functions run in parallel?

Asynchronous operations in parallelThe method async. parallel() is used to run multiple asynchronous operations in parallel. The first argument to async. parallel() is a collection of the asynchronous functions to run (an array, object or other iterable).

Does async await run synchronously?

Top-level code, up to and including the first await expression (if there is one), is run synchronously. In this way, an async function without an await expression will run synchronously. If there is an await expression inside the function body, however, the async function will always complete asynchronously.

How do you call async await?

async and await Inside an async function, you can use the await keyword before a call to a function that returns a promise. This makes the code wait at that point until the promise is settled, at which point the fulfilled value of the promise is treated as a return value, or the rejected value is thrown.


3 Answers

It's easier if you work with promise "creators" (= function that return promises) rather than raw promises. First, define:

const fetchJson = (url, opts) => () => fetch(url, opts).then(r => r.json())

which returns such a "creator". Now, here are two utilities for serial and parallel chaining, which accept both raw promises and "creators":

const call = f => typeof f === 'function' ? f() : f;

const parallel = (...fns)  => Promise.all(fns.map(call));

async function series(...fns) {
    let res = [];

    for (let f of fns)
        res.push(await call(f));

    return res;
}

Then, the main code can be written like this:

let [[getTokenData, writeToDBData], frontEndData] = await parallel(
    series(
        fetchJson(url_for_getToken),
        fetchJson(url_for_writeToDB),
    ),
    fetchJson(url_for_frontEnd),
)

If you don't like the dedicated "creator" wrapper, you can define fetchJson normally

const fetchJson = (url, opts) => fetch(url, opts).then(r => r.json())

and use inline continuations right where series or parallel are called:

let [[getTokenData, writeToDBData], frontEndData] = await parallel(
    series(
        () => fetchJson('getToken'),
        () => fetchJson('writeToDB'),
    ),
    () => fetchJson('frontEnd'), // continuation not necessary, but looks nicer
)

To bring the idea further, we can make series and parallel return "creators" as well rather than promises. This way, we can build arbitrary nested "circuits" of serial and parallel promises and get the results in order. Complete working example:

const call = f => typeof f === 'function' ? f() : f;

const parallel = (...fns)  => () => Promise.all(fns.map(call));

const series = (...fns) => async () => {
    let res = [];

    for (let f of fns)
        res.push(await call(f));

    return res;
};

//

const request = (x, time) => () => new Promise(resolve => {
    console.log('start', x);
    setTimeout(() => {
        console.log('end', x)
        resolve(x)
    }, time)
});

async function main() {
    let chain = series(
        parallel(
            series(
                request('A1', 500),
                request('A2', 200),
            ),
            series(
                request('B1', 900),
                request('B2', 400),
                request('B3', 400),
            ),
        ),
        parallel(
            request('C1', 800),
            series(
                request('C2', 100),
                request('C3', 100),
            )
        ),
    );

    let results = await chain();

    console.log(JSON.stringify(results))
}

main()
.as-console-wrapper { max-height: 100% !important; top: 0; }
like image 171
georg Avatar answered Nov 16 '22 02:11

georg


There are many ways but the most universal is to wrap each async code path in an async function. This gives you flexibility of mix & matching async return values as you please. In your example you can even inline code with async iife's:

await Promise.all([
  (async() => {
    let getToken = await fetch(url_for_getToken);
    let getTokenData = await getToken.json();

    let writeToDB = await fetch(url_for_writeToDB);
    let writeToDBData = await writeToDB.json();
  })(),
  (async() => {
    let frontEnd = await fetch(url_for_frontEnd);
    let frontEndData = await frontEnd.json();
  })()
]);
like image 33
marzelin Avatar answered Nov 16 '22 04:11

marzelin


You can use .then(), rather than await:

fetch(url_for_getToken)
  .then(getToken => getToken.json())
  .then(async getTokenData => {
    let writeToDB = await fetch(url_for_writeToDB);
    let writeToDBData = await writeToDB.json();
    // Carry on
  })

fetch(url_for_frontEnd)
  .then(frontEnd => frontEnd.json())
  .then(frontEndData => {
    // Carry on  
  })
like image 43
Kobe Avatar answered Nov 16 '22 04:11

Kobe