JavaScript has a concurrency model based on event loop. Each message is processed completely before any other message is processed. This offers some nice properties when reasoning about your program, including the fact that whenever a function runs, it cannot be pre-empted and will run entirely before any other code runs (and can modify data the function manipulates).javascript
runYourScript(); while (atLeastOneEventIsQueued) { fireNextQueuedEvent(); };
Let's use setTimeout as a simple example:html
let start = +new Date; setTimeout(function Task1(){ let end = +new Date; console.log(`Task1: Time elapsed ${end - start} ms`); }, 500); //500ms later,Task1 will be inserted into event queue // single thread setTimeout(function Task2(){ let end = +new Date; console.log(`Task2: Time elapsed ${end -start} ms`); /** * use while loop to delay the completion of the function for 3 seconds * this will block the execution of the next function in event loop * i.e. looping through the event queue has to happen after the main thread finishes its task */ while(+new Date - start < 3000) {} }, 300); //300ms later,Task2 will be inserted into event queue while(+new Date - start < 1000) {} //main thread delay completion for 1 second console.log('main thread ends'); //output: //main thread ends //Task2: Time elapsed 1049 ms //Task1: Time elapsed 3000 ms
setTimeout will put the first argument(type Function) into event queue. Here is what happens in the code above:java
Asynchronous functions in JavaScript usually can accept a last parameter of function type (it's usually called callback) and the callback will be inserted into event queue when the function completes. As the callback is in event queue, the function is NON-BLOCKING. Asynchronous functions can guarantee the following unit test will always pass:node
let functionHasReturned = false; asyncFunction(() => { console.assert(functionHasReturned); }); functionHasReturned = true;
Note that NOT all functions with a callback parameter are asynchronous. E.g. Array.prototype.forEach is synchronous.git
let before = false; [1].forEach(() => { console.assert(before); }); before = true;
Since the callback function is put in the event queue and executed later with it's own invoking context, wrapping the asynchronous functions with try-catch mechanism won't be able to catch the exception from the callback.github
try { setTimeout(() => { throw new Error('callback error'); }, 0); } catch (e) { console.error('caught callback error'); } console.log('try-catch block ends');
In the example, the exception thrown in the callback is not caught by our own program (i.e. the catch block - no 'caught callback error' is printed on the console). We can catch these uncaught exceptions with window.onerror in brower and process.uncaughtException in Node.api
If we want to deliberately catch the error, as we should always do, we can do it in the callback. E.g.app
let fs = require('fs'); fs.readFile('abc.txt', function(err, data) { if (err) { return console.error(err); }; console.log(data); });