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

前端 未结 4 595
鱼传尺愫
鱼传尺愫 2020-11-28 15:39

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         


        
相关标签:
4条回答
  • 2020-11-28 15:49

    The other answers talked about the problems multi-threading and parallelism introduce. However, I want to address your answer directly.

    Why not? (As in, "why couldn't there be?"... I'm not interested in a history lesson)

    Absolutely no reason. ECMAScript - the JavaScript specification says nothing about concurrency, it does not specify the order code runs in, it does not specify an event loop or events at all and it does not specify anything about blocking or not blocking.

    The way concurrency works in JavaScript is defined by its host environment - in the browser for example that's the DOM and the DOM specifies the semantics of the event loop. "async" functions like setTimeout are only the concern of the DOM and not the JavaScript language.

    Moreover there is nothing that says JavaScript runtimes have to run single threaded and so on. If you have sequential code the order of execution is specified, but there is nothing stopping anyone from embedding the JavaScript language in a multi threaded environment.

    0 讨论(0)
  • 2020-11-28 15:50

    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.

    0 讨论(0)
  • 2020-11-28 16:01

    Because Javascript interpreters are single-threaded, event driven. This is how the initial language was developed.

    You can't do "use noblock" because no other work can occur during that phase. This means your UI will not update. You cannot respond to mouse or other input event from the user. You cannot redraw the screen. Nothing.

    So you want to know why? Because javascript can cause the display to change. If you were able to do both simultaneously you'd have all these horrible race conditions with your code and the display. You might think you've moved something on the screen, but it hasn't drawn, or it drew and you moved it after it drew and now it's gotta draw again, etc. This asynchronous nature allows, for any given event in the execution stack to have a known good state -- nothing is going to modify the data that is being used while this is being executed.

    That is not to say what you want doesn't exist, in some form.

    The async library allows you to do things like your parallel idea (amongst others).

    Generators/async/wait will allow you to write code that LOOKS like what you want (although it'll be asynchronous by nature).

    Although you are making a false claim here -- humans are NOT bad at writing asynchronous code.

    0 讨论(0)
  • 2020-11-28 16:05

    So why isn't possible for javascript compilers/interpreters to just NOT block on the statements we currently know as "blocking"?

    Because of concurrency control. We want them to block, so that (in JavaScript's single-threaded nature) we are safe from race conditions that alter the state of our function while we still are executing it. We must not have an interpreter that suspends the execution of the current function at any arbitrary statement/expression and resumes with some different part of the program.

    Example:

    function Bank() {
        this.savings = 0;
    }
    Bank.prototype.transfer = function(howMuch) {
        var savings = this.savings;
        this.savings = savings + +howMuch(); // we expect `howMuch()` to be blocking
    }
    

    Synchronous code:

    var bank = new Bank();
    setTimeout(function() {
        bank.transfer(prompt); // Enter 5
        alert(bank.savings);   // 5
    }, 0);
    setTimeout(function() {
        bank.transfer(prompt); // Enter 3
        alert(bank.savings);   // 8
    }, 100);
    

    Asynchronous, arbitrarily non-blocking code:

    function guiPrompt() {
        "use noblock";
        // open form
        // wait for user input
        // close form
        return input;
    }
    var bank = new Bank(); 
    setTimeout(function() {
        bank.transfer(guiPrompt); // Enter 5
        alert(bank.savings);      // 5
    }, 0);
    setTimeout(function() {
        bank.transfer(guiPrompt); // Enter 3
        alert(bank.savings);      // 3 // WTF?!
    }, 100);
    

    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?

    For simplicity and security, see above. (And, for the history lesson: That's how it just was done)

    However, this is no longer true. With ES6 generators, there is something that lets you explicitly pause execution of the current function generator: the yield keyword.

    As the language evolves, there are also async and await keywords planned for ES7.

    generators [… don't …] lead to code as simple and easy to understand as the sync code above.

    But they do! It's even right in that article:

    suspend(function* () {
    //              ^ "use noblock" - this "function" doesn't run continuously
        try {
            var foo = yield getSomething();
    //                ^^^^^ async call that does not block the thread
            var bar = doSomething(foo);  
            console.log(bar); 
        } catch (error) {
            console.error(error);
        }
    })
    

    There is also a very good article on this subject here: http://howtonode.org/generators-vs-fibers

    0 讨论(0)
提交回复
热议问题