Chain async functions
Asked Answered
C

5

40

In an async function, I can get an asynchronous value like so:

const foo = await myAsyncFunction()

If I want to call a method on the result, with a sync function I'd do something like myAsyncFunction().somethingElse()

Is it possible to chain calls with async functions, or do you have to assign a new variable for each result?

Condemnatory answered 28/7, 2016 at 19:3 Comment(4)
Don't know about ES7, but in other languages you use parentheses around the await expression to chain functions.Moriarty
await myAsyncFunction().then(x => x.somethingElse()) :-)Mailbag
async/await is not part of ES7.Youngstown
For any one who reads this and came from a Selenium for node snippet, functions like findElement returns a special WebElementPromise and not a regular Promise, which can be chained with fields and it will eventually return the result/returned value from the chained property/function (When it's a Promise, you can add await before everything, therefore this: await driver.findElement(By.css("button")).click(); is valid, and it's not needed to await (await driver.findElement(By.css("button"))).click();)Immoderacy
S
56

I prefer to assign the first result to an intermediate variable, I personally find it more readable.

If you prefer, you can await in an expression, no need to assign it. All you have to do is to use parentheses. See my example:

const foo = await (await myAsyncFunction()).somethingElseAsync()

Or if you want to call a sync method on the result:

const foo = (await myAsyncFunction()).somethingElseSync()
Sparrow answered 28/7, 2016 at 19:5 Comment(2)
what if the first method is sync and the second async?Indole
nvm, the problem was my second function didn't return anything ;)Indole
S
15

I'm surprised that it's not already mentioned, but probably the most readable way to achieve this without extra variables is to use a .then chain, and await that at the end:

await myAsyncFunction()
  .then(res => res.somethingElse())

Remember, await works together with promises, but doesn't replace them!

Shaffert answered 22/5, 2021 at 14:56 Comment(2)
I wouldn't mix await and then, they are on different levels of abstraction.Sparrow
@TamasHegedus This is up to personal preference. In my opinion, as long as it improves readability, it is OK. I don't like blindly mixing them either, but when I have a series of short actions performed on a single value (e.g. async method calls, as is the case here), I prefer a .then chain awaited at the end. (Off-topic: Üdv Magyarországról :)Shaffert
F
4

you can try this package.

// Instead of thens
fetch(url)
  .then(res => res.json())
  .then(json => json.foo.bar)
  .then(value => console.log(value))

// Instead of awaits
const res = await fetch(url)
const json = await res.json()
const value = json.foo.bar
console.log(value)

// With prochain
const value = await wrap(fetch(url)).json().foo.bar
console.log(value)
Faller answered 22/5, 2021 at 14:35 Comment(0)
F
1

This answer by Tamas Hegedus with parentheses around the await expressions is definitely the way to go with vanilla JavaScript.

Alternatively, if you're looking for commonly chained JS methods and don't mind a third-party module you can use async-af on npm. With that you can chain asynchronous methods like so:

const promises = [1, 2, 3].map(n => Promise.resolve(n));

AsyncAF(promises).map(n => n * 2).filter(n => n !== 4).forEach(n => console.log(n));
// logs 2 then 6
<script src="https://unpkg.com/[email protected]/index.js"></script>
Fenton answered 21/6, 2018 at 14:38 Comment(0)
S
0

If you want to omit calling then(d => d.method()) everytime, you could wrap your async calls in a small helper function:

const buy = {
  cart: [],
  apple: async function() {
    setTimeout(() => {
      this.cart.push('apple');
      console.log(this.cart);
    }, 1000);
    return this;
  }
};

function chain(object, ...asyncMethods) {
  const methods = [...asyncMethods];

  methods.reduce((acc, curr) => {
    // if object is not a promise (first iteration), call the method 
    if (!acc.then) {
      const promise = curr.call(acc);
      return promise;
    } else {
      // if object is a promise, resolve it, then call the method
      return acc.then((d) => curr.call(d));
    }
  }, object);
}

chain(buy, buy.apple, buy.apple, buy.apple);

// ['apple']
// ['apple', 'apple']
// ['apple', 'apple', 'apple']

However, this won't work if you have to pass arguments to your method. In that case, you could pass the function calls as objects like {buy: "orange"}, then destructure them in your helper.

Stirpiculture answered 26/4, 2023 at 11:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.