The original link

The CertSimple website recently published an article saying that async and await in ES2017 are the best features of JS. I couldn’t agree more.

Basically, one of the few advantages of JS is that it handles asynchronous requests properly. This is thanks to the functions and closures it inherits from Scheme.

However, this is one of the biggest problems with JS because it leads to callback hell, a seemingly unavoidable problem that makes asynchronous JS code very unreadable. Many solutions have been tried and mostly failed to solve callback hell. The Promise scheme came close to solving this problem, but it failed.

Finally, we saw a scheme combining async/await with Promise that solved the problem very well. In this article I will explain why this is the case and the relationship between Promise, async/await and do syntax, and monad.

First, we tried three different styles of code to fetch and read the balances in all accounts of the user. (A user has more than one account accout, each account has a balance balence)

Wrong scheme: Callback hell


function getBalances(callback) { 
  api.getAccounts(function (err, accounts) { / / callback
    if (err) {
      callback(err);
    } else {
      var balances = {}; / / the balance
      var balancesCount = 0; 
      accounts.forEach(function(account, i) {
        api.getBalance(function (err, balance) { / / callback
          if (err) {
            callback(err);
          } else {
            balances[account] = balance;
            if (++balancesCount === accounts.length) {
              callback(null, balances); }}}); }); }}); };Copy the code

This is an easy way to think about it, but it has two layers of callbacks, and there are three problems with this ugly code that need to be addressed:

  1. Err is handled everywhere
  2. Use counters to calculate asynchronously obtained values
  3. Inevitable nesting

Almost right: Promise


function getBalances() {
  return api.getAccounts()
    .then(accounts= > 
        Promise.all(accounts.map(api.getBalance))
            .then(balances= > Ramda.zipObject(accounts, balances))
    );
}
Copy the code

This code addresses the above three problems:

  1. We can handle errors uniformly in the last THEN
  2. Promise.all eliminates the need to define additional counters
  3. Nesting can be avoided to the greatest extent possible

However, there is still a problem that then is still nested. The second THEN is in the callback of the first THEN, because the second THEN needs the accounts variable of the first then. So it’s important to indent your code correctly.

The first THEN passes accounts to the second then:

function getBalances() {
  return api.getAccounts()
    .then(accounts= > Promise.all(accounts.map(api.getBalance)
                                       .then(balances= > [accounts, balances])))
    .then(([accounts, balances]) = > Ramda.zipObject(accounts, balances));
}
Copy the code

But that leads to another “THEN”. You can see that Promise basically solved the callback below, but not completely.

Correct scheme: async/await


async function getBalances() {
  const accounts = await api.getAccounts();
  const balances = await Promise.all(accounts.map(api.getBalance));
  return Ramda.zipObject(balances, accounts);
}
Copy the code

An async function can have an await keyword, and an await gets a Promise to complete the task before the next sentence is executed.

And with that we don’t have to indent the pain anymore. How does this work? We need to go back to the source.

Call back to the origins of Hell

Many people think of callback hell as something that only happens in asynchronous tasks, but it actually happens whenever we use callbacks to deal with wrapped values.

Suppose you want to print out [1, 2, 3] [4 and 6] [7,8,9] all the permutation and combination, such as,4,7 [1],4,8 [1], etc. :

[1.2.3].map((x) = >{[4.5.6].map((y) = >{[7.8.9].map((z) = > { 
      console.log(x,y,z); })})});Copy the code

Look, the familiar callback hell has emerged. This is completely synchronous code, but async and await can only handle async…

Assuming we create a similar keyword called multi/pick for the synchronous code, the above code can be written as

multi function () {
  x = pick [1.2.3];
  y = pick [4.5.6];
  z = pick [7.8.9];
  console.log(x, y, z);
}
Copy the code

Of course, this syntax doesn’t exist.

Monad and do

Some languages have features that can handle all of these requirements and make no distinction between asynchronous and synchronous.

The intermediate process requires some TS and Haskell knowledge, please read for yourself. The code looks something like this:

getBalances: :Promise (Map String String) This is the type declaration
getBalances = do 
  accounts <- getAccounts
  balances <- getBalance accounts
  return (Map.fromList (zip accounts balances))
Copy the code

This syntax is called the DO tag or do syntax. It requires promises to meet some of Monad’s rules.

The DO syntax and Monad were used in Haskell in 1995.

These two features have since solved callback hell. If you compare JS Promise, await/async with Haskell’s Monad, do syntax, you’ll see

Await /async is to Promise what do syntax is to Monad

Now that Haskell has proven that Monad can effectively avoid callback hell, JS can simply rely on await.

conclusion

JS is great again. But why did it take JS so long to borrow from Monad? If only the community had followed the advice of “that crazy guy” in 2013.

The full text.

What did that crazy guy say? Open the link and you’ll see a GitHub Issues page with the guy’s name Brian Mckenna.

Brian suggested using a functional programming approach to optimize Promise.

Domenic, the bill’s defender, was unsympathetic.

Was said

We’re not going to do that. This solution is impractical and creates strange and useless apis that can’t be used in JS to satisfy someone’s own aesthetic preferences. What you don’t understand is that the problem Promise addresses is providing an asynchronous flow control model in an imperative programming language. This scenario was hilariously inaccurate, as it failed to meet our specs and should only pass 1/500 of our test cases.

The response got 16 likes and 254 clicks.