Hey! async/await a minute!

https://en.wikipedia.org/wiki/Fathers_and_Sons_(novel)

How “Fathers and Sons” linked to the ES6 Async/Await feature? So — it is quite easy — the are the Sons. And Promises are the Fathers.

Even if you never read this book you should understand the problem — old generation cant understand youth, youth will not understand that “old stupid generating” actually have given them a birth. And then they will repeat the fate of fathers.

In the same time — they are same. Just cant understand it. But lets back to async/await.

The origin of async/await

As long async is a brand new feature of ES6 — it is still Promise based. If you are async — then you will return a promise, and if you are await — you will wait for a promise.

The only difference — generators. Then you await for something — you just suspend execution of the current function, and next resume from the same place. That’s how generators work — as suspendable(resumable) function.

Control flow

The main difference between async/await and Promises is how the work. Here you can found nice explanation, but let me repeat.

async function solution() {
// "blocking" call
console.log(await rp('http://example.com/'));

// Spawn the HTTP concurrently
const call2Promise = rp('http://example.com/'); // no wait
const call3Promise = rp('http://example.com/'); // no wait

// After they are both spawn - wait for both of them
const response2 = await call2Promise;
const response3 = await call3Promise;
}

The same in promises:

rp('http://example.com/')
.then(result1 => {
// Executes after the first request has finished
console.log(result1);

return Promise.all([
rp('http://example.com/'),
rp('http://example.com/')
]);
});

So — async/await is a cool thing if you execute something step-by-step, but not a thing if you have something more complex, cos you have to add some strange code, and you have to understand how all it works to get a good result.

Complex things are still doable, but you will start use… promises.

// Encapsulate the solution in an async function
async function solution() {
console.log(await rp('http://example.com/'));

await Promise.all([
rp('http://example.com/'),
rp('http://example.com/')
]);
}

Anyway — the control flow, or how one step can provide a variable to another, and rest of awesome features of all this stuff — is not a goal of this article.

We need a problem!

Conflict of Fathers and Sons

The War! Conflict! Misunderstandings! Blood! More blood!

Lets recall that is Promise:

Promise will took Resolve or Reject as an input, and return Resolve or Reject as an output.

And it was absolutely common to Resolve something with positive value, and reject with negative. Left and Right. Light and Dark. Forks. Pipes.

This is just a control flow. And you can control it.

// if condition
if (midnight) {
gotoSleep();
} else {
workWorkWork();
}
// promise condition
Midnight
.then(gotoSleep, workWorkWork);
// something more complex
someFormSubmission
.then(validate)
.then(send)
.catch(showAHugeError);
// or a bit simpler
someFormSubmission
.then(loggingAsASideEffect)

You got two streams of data as input, you provide two streams as output. You can fork, split and join streams back. You can do anything…

So what is the problem?

The problem is quite simple — one (the main) feature of async/await is a code execution in a “straight line”. One line after another.

As result — if you await something, you will get the result of that async operation. The Resolve. Next statement after await — is resolve.

But where is Reject?

As long there is no way to place Reject path here — reject will throw an Exception.

try {
const isMidnight = await Midnight;
// isMidnight always true.
} catch (err) {
// isMidnight always NOT true. Even not undefined - out of scope
}

And this is absolutely ok. Millions do the same before the Promises and async APIs — try to read file, and catch the exception.

Just the youngest generation look like grandfathers, not fathers.

Grandfathers?

Father of Promise, and grandy of async/await is Callback, and his father is The Synchronous. Btw, this is quite strange:

  • Promises are using callback in all cases.
  • Async/await uses Promices. And NOT using any callbacks.
  • But async/await behave as grand-grandfathers.
  • And to live in peace with kids Promises have to behave like Callbacks.
Promises have to adopt, to find the common language with their children.

So the new approach is simple as a rock:

If you have result — resolve with result.
Throw an Exception in case of Exception.

And Error is not an Exception. It is just not very good result.

POSIX API, for example, is usually returns “-1” if something went wrong. And next you can checkout some details in errno variable.

If you tried to open a file, and failed — that is not an exception. That is an absolutely normal and predicted event. File-not-found is not the end of the world.

Exception is an unpredicted event.

What next?

There is also one quite important difference.

Promises are chains of small functions. Async/Await is one big function. So — there is a huge difference in testability, reusability and splitability. You cannot add log as “no thought” side effect as you can do it with Promises, and you can’t inject some step in the middle of execution as “natively” as you can do it with Promises.

You still can do it, but it will require a different approach.

So what?

So — nowadays some libraries are changing their promise-based APIs to better fit async/await.

Now, they are close to node.js API — where callback was used both for error and success path. Or even closer to the PHP code.

$query = mysql_query("SELECT FROM BLA-BLA") or die(mysql_error())
Fathers in a try to be closer to their children. Kids discovering the roots.

Thats ok, but they are changing themselves away from themselves. Afterwards Promises become less “Promises”. More usable for new generation, less usable for the old school.

And that means, that is quite hard to create some async library with good interfaces both for old Promise-based interfaces, and for a new async ones.

That is the life. Just look at your self, and then at your parents.

Like what you read? Give Anton Korzunov a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.