Skip to content
Advertisement

Why couldn’t popular JavaScript runtimes handle synchronous-looking asynchronous script?

As cowboy says down in the comments here, we all want to “write [non-blocking JavaScript] asynchronous code in a style similar to this:

 try 
 {
    var foo = getSomething();   // async call that would normally block
    var bar = doSomething(foo);  
    console.log(bar); 
 } 
 catch (error) 
 {
    console.error(error);
 }

So people have come up solutions to this problem like

But none of these lead to code as simple and easy to understand as the sync-style code above.

So why isn’t possible for javascript compilers/interpreters to just NOT block on the statements we currently know as “blocking”? So why isn’t possible for javascript compilers/interpreters to handle the sync syntax above AS IF we’d written it in an async style?”

For example, upon processing getSomething() above, the compiler/interpreter could just say “this statement is a call to [file system/network resource/…], so I’ll make a note to listen to responses from that call and in the meantime get on with whatever’s in my event loop”. When the call returns, execution can proceed to doSomething().

You would still maintain all of the basic features of popular JavaScript runtime environments

  • single threaded
  • event loop
  • blocking operations (I/O, network, wait timers) handled “asynchronously”

This would be simply a tweak to the syntax, that would allow the interpreter to pause execution on any given bit of code whenever IT DETECTS an async operation, and instead of needing callbacks, code just continues from the line after the async call when the call returns.

As Jeremy says

there is nothing in the JavaScript runtime that will preemptively pause the execution of a given task, permit some other code to execute for a while, and then resume the original task

Why not? (As in, “why couldn’t there be?”… I’m not interested in a history lesson)

Why does a developer have to care about whether a statement is blocking or not? Computers are for automating stuff that humans are bad at (eg writing non-blocking code).

You could perhaps implement it with

  • a statement like "use noblock"; (a bit like "use strict";) to turn this “mode” on for a whole page of code. EDIT: "use noblock"; was a bad choice, and misled some answerers that I was trying to change the nature of common JavaScript runtimes altogether. Something like 'use syncsyntax'; might better describe it.
  • some kind of parallel(fn, fn, ...); statement allowing you to run things in parallel while in "use syncsyntax"; mode – eg to allow multiple async activities to be kicked off at once
  • EDIT: a simple sync-style syntax wait(), which would be used instead of setTimeout() in "use syncsyntax"; mode

EDIT:

As an example, instead of writing (standard callback version)

function fnInsertDB(myString, fnNextTask) {
  fnDAL('insert into tbl (field) values (' + myString + ');', function(recordID) {
    fnNextTask(recordID);
  });
}

fnInsertDB('stuff', fnDeleteDB);

You could write

'use syncsyntax';

function fnInsertDB(myString) {
  return fnDAL('insert into tbl (field) values (' + myString ');');  // returns recordID
}

var recordID = fnInsertDB('stuff'); 
fnDeleteDB(recordID);

The syncsyntax version would process exactly the same way as the standard version, but it’s much easier to understand what the programmer intended (as long as you understand that syncsyntax pauses execution on this code as discussed).

Advertisement

Answer

Why not? No reason, it just hadn’t been done.

And here in 2017, it has been done in ES2017: async functions can use await to wait, non-blocking, for the result of a promise. You can write your code like this if getSomething returns a promise (note the await) and if this is inside an async function:

try 
{
    var foo = await getSomething();
    var bar = doSomething(foo);  
    console.log(bar); 
} 
catch (error) 
{
    console.error(error);
}

(I’ve assumed there that you only intended getSomething to be asynchronous, but they both could be.)

Live Example (requires up-to-date browser like recent Chrome):

function getSomething() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if (Math.random() < 0.5) {
                reject(new Error("failed"));
            } else {
                resolve(Math.floor(Math.random() * 100));
            }
        }, 200);
    });
}
function doSomething(x) {
    return x * 2;
}
(async () => {
    try 
    {
        var foo = await getSomething();
        console.log("foo:", foo);
        var bar = doSomething(foo);  
        console.log("bar:", bar); 
    } 
    catch (error) 
    {
        console.error(error);
    }
})();
The first promise fails half the time, so click Run repeatedly to see both failure and success.

You’ve tagged your question with NodeJS. If you wrap the Node API in promises (for instance, with promisify), you can write nice straight-forward synchronous-looking code that runs asynchronously.

User contributions licensed under: CC BY-SA
6 People found this is helpful
Advertisement