I know this is old, but I came across this searching for the same thing, am not satisfied with any of the answers, and see that it gets a new answer every few years, so I'll add mine.
TL;DR;
Javascript is synchronous. Any running code will block any other code that tries to run. Web Workers are synchronous, but they are essentially a new, hidden, browser window (a separate thread) so they are executing their code synchronously, separately. What you can do is run your code in an indeterminate order. You can use events, setTimeout/setInterval, promises, etc to break your code into separate chunks with different trigger criteria and let the browser run them in whatever order that criteria ends up producing. This gives a semblance of asynchronicity, but the browser will still only run one chunk at a time, and that chunk will have to complete before the next chunk is run, regardless of whether those chunks run any of the same routines or not. You can use web workers to move code into a separate thread, but that code is truly separate, with no asynchronous interaction with the original code. The worker code is still synchronous within its thread, and communication between the worker and the browser still won't let either one run more than one thing at a time (ie, a worker isn't going to process an event if it's doing something else). In a lot of languages you have to jump through hoops to make sure your code works if it ever happens to be run by multiple threads. In Javascript, those issues are not possible, and you would have to intentionally jump through a lot of hoops just to simulate those types of problems.
This fiddle shows a comparison between async and promises, demonstrating basically what an async function really is and what long-running tasks do. (https://jsfiddle.net/g23ptkxz/)
This fiddle shows a basic web worker and what happens when the worker is running a long task, proving the lack of asynchronicity between the threads. (https://jsfiddle.net/wojqad3x/)
Javascript is synchronous
Javascript is synchronous, period. Anything running in a webpage (even if it's multiple imported script files) will block anything else that tries to run. What callbacks let you do is hand a function to the browser to be run at some point later. So when you call setTimeout, you aren't executing javascript code asynchronously, you are just handing it to the browser so that it can be executed synchronously, but out of order, later.
Using setTimeout as an example, you tell the browser to run it when a certain amount of time passes. The browser will then ATTEMPT to call your function. But that function is still part of your web page, so if you are still doing anything, you will block the browser and it will have to wait. So if you call setTimeout(x,3000), and then do some work that takes 20 seconds, it will be at least 20 seconds before x gets called.
Promises
Promises are the exact same thing. I don't truly even consider the function you pass to the constructor of a Promise a "callback"; it is executed immediately just as if you had replaced the new Promise() line with the constructor function code. So
doSomething();
new Promise((resolve, reject) => {
doSomething2();
doSomething3();
resolve();
});
doSomething4();
is the exact same thing as
doSomething();
doSomething2();
doSomething3();
doSomething4();
So the constructor function definitely blocks; doSomething4 isn't going to be run until doSomething3 completes, regardless of whether it is in a promise constructor function or not. Now, the "then" portion is just like the setTimeout, where you are handing that function off to the browser. But instead of saying "run this when this much time passes", you are telling it "run this after I call the resolve callback". So again, that "then" function is part of your existing script. So if you call resolve and then continue doing some long work (eg, doSomething4() takes a long time) the "then" function still isn't going to get called for a while, because the browser will have to wait.
One fancy bit of this is that the browser is guaranteed to wait. So when the promise spec talks about "guaranteed to be asynchronous", it means "guaranteed to run out of order".
doSomething();
new Promise((resolve, reject) => {
doSomething2();
doSomething3();
resolve();
}).then(doSomething5);
doSomething4();
In this example, doSomething5 is guaranteed to not be called until after doSomething4, even though the promise has been resolved before the promise constructor even completes. And if you've already had several other setTimeouts or events or whatever that have been blocked, those are all going to run before doSomething5 does.
Async/Await
Async/Await is just a wrapper around promises. Your code goes into the constructor, so if you don't call anything that does an await, your function executes completely before the next line after the call is executed, regardless of whether you call the function with async or not.
async function doStuff() {
doSomething2();
doSomething3();
}
doSomething1();
await doStuff();
doSomething4();
is essentially
function doStuff() {
const p = new Promise((resolve,reject) => {
doSomething2();
doSomething3();
resolve();
});
return p;
}
doSomething1();
doStuff().then(() => {
doSomething4();
});
If this is all you have, the execution is identical to running without async/await. The differences come in that "guaranteed out of order" stuff; if done this way...
async function doStuff2and3() {
doSomething2();
doSomething3();
}
async function doStuff1to4() {
doSomething1();
await doStuff2and3();
doSomething4();
}
doStuff1to4();
doSomething5();
the "then" becomes important. In this case, since doSomething4 is essentially in a "then" and guaranteed to run out of order, and you didn't call doStuff1to4 with "await", doSomething4 won't run until after doSomething5.
I have a fiddler here (https://jsfiddle.net/g23ptkxz/) that has a long-running task written in promises and async, showing that they act exactly the same.
Web Workers are Synchronous
Workers throw a slight (very slight) wrench in the works. The easiest way to think of it is that a web worker is another browser window (I think the technical term is "execution context"). The "new browser window" can do whatever it wants and it has nothing to do with whatever you are doing. But that's where the asynchronicity ends. The code in the web worker will block other code in the web worker just like your main code does. If the web worker is doing anything and you try to post a message to it, that message isn't going to go in until the web worker isn't doing anything. But, since that communication is another "outside of javascript" thing, it won't block your browser; your message just goes into the same queue as any other currently-blocked events or whatever and will execute in the worker whenever everything before it finishes. The main thread has no knowledge of when that is, since the postMessage call is "fire and forget".
I have a fiddle here (https://jsfiddle.net/wojqad3x/) with a basic web worker set up to demonstrate. It spawns a web worker and both the worker and the browser start counters. Clicking the add or sub buttons adjust the number the web worker is generating, and the two scripts post messages back and forth to each other. If you click "longRunner", you'll see that the counter for the worker stops because the message event locks up the worker and blocks everything else. The main browser isn't locked, so that counter keeps going. You can click add1 and sub1 while longRunner is active and you'll see that none of their processing gets done until after longRunner completes. And when longRunner does complete, it generates a message that includes the current total adjustment that add1 and sub1 have made, so you can see it isn't just the messaging display that has stopped, the adjustment doesn't get applied at all until longRunner finishes.
Node's process.nextTick
process.nextTick(foo) is exactly the same thing as Promise.resolve().then(foo). Process.nextTick is node's way of saying "I guarantee foo will be run out of order". It does the same thing "then" does; it takes that function call and adds it to the event queue to be run after whatever is currently running finishes.
(Updated version of the link from Adam's answer. (https://howtonode-org.vercel.app/understanding-process-next-tick.html))
What it all boils down to
The main takeaway is that you don't have to worry about thread safe coding and race conditions and such in javascript. No section of code will ever be run by multiple things.
const _list = [];
function addItem(item) {
_list.push(item);
startWatcher();
}
function removeItem(itemKey) {
for (let i = 0; i < _list.length; ++i) {
if (_list[i].key == itemKey) {
_list.splice(i, 1);
break;
}
}
}
let _watcherHandle;
function startWatcher() {
if (_watcherHandle) return;
function handler() {
for (let i = _list.length - 1; i >= 0; --i) {
if (_list[i].needsRemoval) {
removeItem(_list[i].key);
}
}
}
_watcherHandle = startInterval(handler, 1000);
}
function stopWatcher() {
clearInterval(_watcherHandle);
_watcherHandle = 0;
}
This section of code is a nightmare in an asynchronous language. Something could be added between the check for the length of the array and the time the interval is cleared, so that the interval stops with an item in the array. Or it could be added after the interval is cleared but before the handle is set to 0, so the interval doesn't restart. Or something could be inserted into the array that needs to be removed while the array is already being processed, so it would get missed until the next interval. Some other thread could completely empty the array while the watcher was still processing and cause errors with invalid indexes.
In javascript, none of that can happen. It's using setInterval, so you don't know exactly when the watcher handler will be called. Any of the other routines may be called by any number of promises, or events (even message events from another worker), or other intervals/timeouts, so you may not know the ORDER of any of the executions beforehand. But you DO know that it is all one script, so if any section of that code is executing, it's going to execute to completion with no interference. If, for some masochistic reason, you wanted to have the possibility of race conditions and such, you would have to break every line out into its own promise/async/eventHandler or whatever so that you didn't know the order that the individual operations within a call would happen in. It's not something you do by accident.