2

I am trying a callback function

function doAsyncTask(cb) {
  cb();

}

doAsyncTask(() => {
  console.log(message);
})

let message = 'Callback Message'

This gives error that message is undefined, which is quite understood.

But if I do

function doAsyncTask(cb) {
  setTimeout(() => {
    cb();
  })
}

doAsyncTask(() => {
  console.log(message);
})

let message = 'Callback Message'

This works when I make this code async.

I could not understand what this async/setTimeout() does so that the scope of let is changed and it is available to the callback function.

Please help me understanding this.

Ele
  • 33,468
  • 7
  • 37
  • 75
raju
  • 6,448
  • 24
  • 80
  • 163
  • This is an event-loop concept... – Ele Mar 12 '19 at 02:18
  • This the most amazing expatiation of event loop by Philip Roberts. I think it will clear all your doubt. [event loop](https://www.youtube.com/watch?v=8aGhZQkoFbQ) – Rahul Raut Mar 12 '19 at 02:58

4 Answers4

3

Explanation

What you've run into can be found in this question.

setTimeout, in simple english, basically delays the execution of its callback function to the "next tick" of the execution.

Basically, the execution context is parsed and queued up. When the browser executes any setTimeout, the browser queues its callbacks to the end of the execution queue.

See the code below for extra understanding.

Using setTimeout

console.log('Queue', 1, '`timeout` function is defined.');
timeout = function(cb){
  console.log('Queue', 3, '`timeout` function is executed.');
  setTimeout(cb, 0);
}

console.log('Queue', 2, '`timeout` function is called');
timeout(function(){
  console.log('Queue', 5, '`timeout` callback is executed.');
  console.log(message)
});

console.log('Queue', 4, '`messeage` variable is defined.');
let message = 'Ok.';

If you see the sequence of the logging, you can see that the value of message variable is already available when the cb is executed.

NOT using setTimeout

Now, compared the above code to this code below, you can see that without setTimeout, the cb execution is not "queued" behind but is called instantly. Making the initialisation execution of message variable execute after cb is called (which throws an error).

console.log('Queue', 1, '`timeout` function is defined.');
timeout = function(cb){
  console.log('Queue', 3, '`timeout` function is executed.');
  cb();
}

console.log('Queue', 2, '`timeout` function is called');
timeout(function(){
  console.log('Queue', 5, '`timeout` callback is executed.');
  console.log(message)
});

console.log('Queue', 4, '`messeage` variable is defined.');
let message = 'Ok.';
yqlim
  • 6,898
  • 3
  • 19
  • 43
2

This is related to how the event-loop works, the setTimeout() execution is added to something called Event-table and the js engine continues the execution.

Event-table

This is a data structure which knows that a certain function should be triggered after a certain event. Once that event occurs (timeout, click, mouse move) it sends a notice.

The event-table doesn't execute functions, it's just to keep that function till an event occurs (timeout in this case), after the event occurs, that function will be placed into the Event Queue.

In your case, the Event Queue will have the following statement let message = 'Callback Message' before the event (timeout) occurs.

So, the stack (aka Call stack and LIFO btw) will contain an order of executions as follow:

  1. let message = 'Callback Message'
  2. () => { cb();}

The first statement will create a global variable/attribute into the Window object and then the callback will print the value of message which is an attribute in the Window object.

Ele
  • 33,468
  • 7
  • 37
  • 75
1

In the first snippet doAsyncTask is called before the declaration of message as it comes before it.
In the second snippet you are using setTimeout() for calling cb(). cb will called in the end of all the code.Hence the in second example message is declared before the cb().

The first snippet is same as below

console.log(message);
let message = 'Callback Message'

The second snippet is same as below

let message = 'Callback Message'
console.log(message);
Maheer Ali
  • 35,834
  • 5
  • 42
  • 73
1

This is just because of execution order:

function doAsyncTask(cb) {
  setTimeout(() => cb())
}
doAsyncTask(() => console.log(message))

console.log("message is undefined")
let message = 'Callback Message'
console.log(`message has been initialized to ${message}`)

Outputs:

message is undefined
message has been initialized to Callback Message
Callback Message

Whereas in your first code, since you call cb() directly the line at which message is set hasn't been executed yet:

function doAsyncTask(cb) {
  cb();
}
doAsyncTask(() => console.log(message))

console.log("message is undefined")
let message = 'Callback Message'
console.log(`message has been initialized to ${message}`)

Outputs:

undefined
message is undefined
message has been initialized to Callback Message
cglacet
  • 8,873
  • 4
  • 45
  • 60