36

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?

Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
Asherlc
  • 1,111
  • 2
  • 12
  • 28
  • 1
    Don't know about ES7, but in other languages you use parentheses around the `await` expression to chain functions. – 4castle Jul 28 '16 at 19:06
  • 2
    `await myAsyncFunction().then(x => x.somethingElse())` :-) – Bergi Jul 28 '16 at 19:17
  • `async/await` is not part of ES7. – Felix Kling Aug 12 '16 at 00: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();`) – ATP Jun 06 '23 at 11:56

5 Answers5

53

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()
Tamas Hegedus
  • 28,755
  • 12
  • 63
  • 97
13

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!

FZs
  • 16,581
  • 13
  • 41
  • 50
  • 2
    I wouldn't mix `await` and `then`, they are on different levels of abstraction. – Tamas Hegedus Jul 15 '22 at 14:26
  • 1
    @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 `await`ed at the end. (Off-topic: Üdv Magyarországról :) – FZs Jul 16 '22 at 08:35
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)
Il Harper
  • 57
  • 7
Jie Yi
  • 41
  • 2
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/async-af@7.0.11/index.js"></script>
Scott Rudiger
  • 1,224
  • 12
  • 16
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.

David
  • 11
  • 1