[Compose] Callback is not suitable for Async programming

发布时间 2023-10-17 15:06:04作者: Zhentiw

An example of callback implemnetation for handling async flow:

function fakeAjax(url, cb) {
  var fake_responses = {
    file1: "The first text",
    file2: "The middle text",
    file3: "The last text",
  };
  var randomDelay = (Math.round(Math.random() * 1e4) % 4000) + 1000;

  console.log("Requesting: " + url, "time: ", randomDelay);

  setTimeout(function () {
    cb(fake_responses[url]);
  }, randomDelay);
}

function output(text) {
  console.log(text);
}

// **************************************
// The old-n-busted callback way
const stack = [];
const response = {};
function getFile(file) {
  if (!(file in response)) {
    stack.push(file);
  }
  // CONS:
  // 1. It needs global variable to store extra information
  // 2. The solution is not flexable to apply elsewhere
  // 3. Won't result in a easy to test solution
  fakeAjax(file, function (text) {
    response[file] = text;
    for (let i = 0; i < stack.length; i++) {
      if (stack[i] in response) {
        if (response[stack[i]] !== false) {
          output(response[stack[i]]);
          response[stack[i]] = false;
        }
      } else {
        return;
      }
    }
    output("completed!");
  });
}

// request all files at once in "parallel"
// and print result in sequencial order
// in the end, print "completed!"
getFile("file1");
getFile("file2");
getFile("file3");

 

As we can see, the code is not easy to understand, then most like not easy to maintable and resuable.

Callback is used a lot in Node.js, but it is not good to handle async events, for multi reasons.

 

Callback hell:

If you want events happen in sequence, we have to use nested callback, which results callback hall.

 

Callback is not reasonable

For a complex async program, if we use lots of callback, it is hard to understand which callback will happen at which time. Often developers got lost in the middle.

 

Callback: iversion of contrrol

Some callback we don't have control, we might pass a callback to a third parity library, then this library will control when to call this callback

But still we need to worry about callback is called

  • Not too early
  • Not too late
  • Not too many times
  • Not too few times
  • No lost context
  • No swallowed error

But how can we handle all those trust issues? Sadly nope. 

 

There are many tries to fix the issues, but none of those are good enough:

Separate callback:

Pass two callbacks, one for error, one for success.

Problem is how can we make sure two callbacks won't be called at the same time? how to make sure callback are only trigger once? how to make sure only pass one callback without another?

So basiclly inversion of control problem, lack of trust.

function trySomething(ok, err) {
   setTimeout(() => {
       const num = Math.random()
       if (num > 0.5) {
         ok(num)
       } else {
         err(num)
       }
   }, 1000)
}

trySomething(
  (num) => {
     console.log('success:', num)
  },
  (num) => {
     console.log("Error: ", num)
  }
)

 

"Error first style":

Now we only use one callback function, and force to do error handling. But it has the same issue as "Separate callback".

How to prevent we receive both error and success callback?....

function trySomething(cb) {
   setTimeout(() => {
       const num = Math.random()
       if (num > 0.5) {
         cb(null, num)
       } else {
         cb("Too low!")
       }
   }, 1000)
}

trySomething(
  (err, num) => {
    if (err) console.log(err)
    else console.log('success:', num)
  }
)