43

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?

4
  • 2
    Don't know about ES7, but in other languages you use parentheses around the await expression to chain functions. Commented Jul 28, 2016 at 19:06
  • 2
    await myAsyncFunction().then(x => x.somethingElse()) :-) Commented Jul 28, 2016 at 19:17
  • async/await is not part of ES7. Commented Aug 12, 2016 at 0:04
  • 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();) Commented Jun 6, 2023 at 11:56

5 Answers 5

57

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()
Sign up to request clarification or add additional context in comments.

2 Comments

what if the first method is sync and the second async?
nvm, the problem was my second function didn't return anything ;)
16

Controversial answer:

To achieve this without extra variables, one way is to use a .then chain, and await that at the end:

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

I personally find this style ok, but some people prefer to avoid it. But you can also just use extra variables if you find that clearer.

2 Comments

I wouldn't mix await and then, they are on different levels of abstraction.
@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 :)
3

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)

Comments

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>

Comments

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.