Javascript asynchronous handling without Promise and async/await

Viewed 42

In this question, I just want to confirm that we cannot use callback to strictly handle asynchronous tasks in JavaScript.
For example, we use Promise.then or async/await:

myPromise().then(callback);
// or
(async () => {
  const res = await myAsyncFunction();
  callback(res);
})()

I saw many examples with callback but they used setTimeout:

const functionWithCallback = (asyncFunction, callback) => {
  asyncFunction();

  setTimeout(() => {
    callback();
  }, 1000);
};

The example above is NOT asynchronous handling to me because we don't know the time asyncFunction() complete. 1000ms here is just a number.
Do we have any way to handle it with callback only?

1 Answers

The example above is NOT asynchronous handling to me

You are right that the timing of the callback invocation does not depend on whether myAsyncFunction completed its asynchronous task or not. However, in the most common use of the terminology, setTimeout is calling its callback function asynchronously... always.

I just want to confirm that we cannot use callback to strictly handle asynchronous tasks in JavaScript.

You can. But there are several things to note:

  • Even promises work with a callback system: the then method takes a callback argument, and that callback will be executed when the promise is fulfilled. Promises do not abolish callbacks, but make it easier to work with them by allowing to chain and to avoid "callback hell".

  • Before promises were available in JavaScript, there surely was already the concept of asynchronous programming.

In your examples you provided a function myAsyncFunction. If it is important to react when its asynchronous tasks have completed, then surely that function must somehow notify of that completion.

Today it is very common for such an asynchronous function to return a promise, and then the caller will of course make use of that promise (with either then or await): that's the purpose of having the promise object. A known example of such an asynchronous function is fetch.

If myAsyncFunction does not return a promise, then we would expect such a function to notify in a different way. For instance, it could take a callback as argument. Then to code looks like this:

myAsyncFunction(callback);

Known examples of such asynchronous functions are setTimeout, requestAnimationFrame, and queueMicrotask

Still other asynchronous functions are more complex, as they may provide some synchronous results and other asynchronous results. They return an object to which a listener can be attached. The code will then look something like this:

let res = myAsyncFunction();
res.addEventListener("ready", callback);

There are many examples of this in the DOM API: for instance, document.createElement and the load event (typically used with img and script elements).

A slight variation of this, is where a constructor is called with new, or where the object is provided first, but the asynchronous action still has to be initiated by some event, or by a method call.

There are of course many variations to this, like:

let res = new MyConstructor();
res.myAsyncFunction();
res.onready = callback;

An example of such a constructor is XMLHttpRequest (with its send() method)

Conclusion

It really depends on how myAsyncFunction will notify that it has completed its asynchronous tasks. When myAsyncFunction returns a promise, then of course you should be using that promise. When not, there should be some way to provide your callback to the API that is offered.

Related