mocking async functions with waitfor

2020-03-21

 | 

~3 min read

 | 

480 words

Frequently, I find myself needing to mock out (or fake) an asynchronous call to test my API I’m developing. I hate having to remember the process every time - so I’m writing it down.

A while back I was researching how to loop over arrays asynchronously. It turns out there are some nuances (a topic for a future post). In that process, I came across a waitFor function that’s been quite useful for mocking out an asynchronous call.

Even better, it’s one line:

index.js
const waitFor = (ms) => new Promise((r) => setTimeout(r, ms))

It takes a waiting period and returns a promise. The promise takes the resolve argument in the new Promise constructor and passes it into a setTimeout with the waiting period.

So, how does this work in practice? Let’s look at an example:

index.js
function fakeTimer() {
  return waitFor(50).then(() => console.log(`successfully waited`))
}

fakeTimer()
console.log(`print first`)

As you might expect, “print first” is … printed first, though eventually the promise is resolved and “successfully waited” will be printed.

This is because the promise was placed into the event loop,1 and when it resolved was popped off.

But what if we want the code to behave more synchronously? Well, we’d need to wrap the entire thing in a function that handles this asynchronisity.

index.js
async function simpleAsync() {
  await fakeTimer()
  console.log(`print after`)
}

Now, we’re successfully waiting for the promise in fakeTimer to resolve before printing the console.log.

As I mentioned, I found this while researching asynchronous mapping of arrays. To see that in practice, we can look at an example of an array with three elements:

index.js
async function fakeAsync() {
  await Promise.all(
    [1, 2, 3].map(async (num) => {
      await waitFor(50).then(() => {
        console.log(num)
      })
    }),
  )
  console.log("Done")
}

When we execute:

fakeAsync()
// 1
// 2
// 3
// Done

Here, instead of simply waiting, we’re looping over an array. Each element is processed before we get to the last console log for “Done” (thanks to the awaits).

I created a repl.it for this called “fake async” to make it easier for me to remember and play with these ideas in the future.

Update: I found another variant of the fakeAsync method that I actually like better:

export const getAsyncData = (dataToReturn, timeToWait, fail = false) =>
  new Promise((resolve, reject) =>
    setTimeout(
      () => (fail ? reject("FAILED") : resolve(dataToReturn)),
      timeToWait,
    ),
  )

The benefit of this approach is that it combines the waitFor with the async method while allowing to also fail. Much better for testing!

Footnotes


Related Posts
  • 2021 Daily Journal


  • Hi there and thanks for reading! My name's Stephen. I live in Chicago with my wife, Kate, and dog, Finn. Want more? See about and get in touch!