search

Home  >  Q&A  >  body text

Will the variables remain unchanged after modification within the function? - Asynchronous code reference

<p>鉴于以下示例,为什么 <code>outerScopeVar</code> 在所有情况下均未定义?</p> <pre class="brush:php;toolbar:false;">var outerScopeVar; var img = document.createElement('img'); img.onload = function() { outerScopeVar = this.width; }; img.src = 'lolcat.png'; alert(outerScopeVar);</pre> <pre class="brush:php;toolbar:false;">var outerScopeVar; setTimeout(function() { outerScopeVar = 'Hello Asynchronous World!'; }, 0); alert(outerScopeVar);</pre> <pre class="brush:php;toolbar:false;">// Example using some jQuery var outerScopeVar; $.post('loldog', function(response) { outerScopeVar = response; }); alert(outerScopeVar);</pre> <pre class="brush:php;toolbar:false;">// Node.js example var outerScopeVar; fs.readFile('./catdog.html', function(err, data) { outerScopeVar = data; }); console.log(outerScopeVar);</pre> <pre class="brush:php;toolbar:false;">// with promises var outerScopeVar; myPromise.then(function (response) { outerScopeVar = response; }); console.log(outerScopeVar);</pre> <pre class="brush:php;toolbar:false;">// with observables var outerScopeVar; myObservable.subscribe(function (value) { outerScopeVar = value; }); console.log(outerScopeVar);</pre> <pre class="brush:php;toolbar:false;">// geolocation API var outerScopeVar; navigator.geolocation.getCurrentPosition(function (pos) { outerScopeVar = pos; }); console.log(outerScopeVar);</pre> <p>为什么在所有这些示例中都输出 <code>undefined</code> ?我不需要解决方法,我想知道<strong>为什么</strong>发生这种情况。</p> <hr /> <blockquote> <p><strong>注意:</strong>这是一个关于 JavaScript 异步性的规范问题。请随意改进这个问题并添加社区可以认同的更多简化示例。</p> </blockquote><p><br /></p>
P粉403804844P粉403804844463 days ago447

reply all(2)I'll reply

  • P粉064448449

    P粉0644484492023-08-29 11:02:10

    Fabrício's answer is very correct; but I would like to supplement his answer with something a little less technical, focusing on helping to explain the concept of asynchronicity through analogies.


    analogy...

    Yesterday, I was working on a job where I needed to get some information from my colleagues. I called him; the conversation went like this:

    Having said that, I hung up the phone. Since I needed the information from Bob to complete my report, I left the report, went to get a cup of coffee, and then I read some emails. 40 minutes later (Bob is slow) Bob called back and gave me the information I needed. At this point, I continue working on my report since I have all the information I need.


    Imagine if the conversation went like this;

    I sat there and waited. and waited. and waited. 40 minutes. Do nothing but wait. Eventually, Bob gave me the information, we hung up, and I finished my report. But I lost 40 minutes of productivity.


    This is asynchronous vs. synchronous behavior

    This is exactly what happens in all the examples in our question. Loading images, loading files from disk, and requesting pages via AJAX are all slow operations (in the context of modern computing).

    JavaScript allows you to register a callback function that will be executed when slow operations complete, instead of waiting for these slow operations to complete. But in the meantime, JavaScript will continue executing other code. The fact that JavaScript is executing other code while waiting for the slow operation to complete makes the behavior asynchronous. If JavaScript waits for an operation to complete before executing any other code, this will be synchronous behavior.

    var outerScopeVar;    
    var img = document.createElement('img');
    
    // Here we register the callback function.
    img.onload = function() {
        // Code within this function will be executed once the image has loaded.
        outerScopeVar = this.width;
    };
    
    // But, while the image is loading, JavaScript continues executing, and
    // processes the following lines of JavaScript.
    img.src = 'lolcat.png';
    alert(outerScopeVar);

    In the above code, we ask JavaScript to load lolcat.png, which is a sloooow operation. Once this slow operation is complete, the callback function will be executed, but in the meantime, JavaScript will continue processing the next line of code; i.e. alert(outerScopeVar).

    This is why we see the alert showing undefined; because alert() is processed immediately, not after the image is loaded.

    To fix our code, all we have to do is move the alert(outerScopeVar) code into the callback function. Therefore, we no longer need to declare the outerScopeVar variable as a global variable.

    var img = document.createElement('img');
    
    img.onload = function() {
        var localScopeVar = this.width;
        alert(localScopeVar);
    };
    
    img.src = 'lolcat.png';

    You will always see callbacks specified as functions because that's the only way in JavaScript to define some code but then execute it later.

    So, in all our examples, function() { /* Do Something */ } is the callback; to fix all examples, all we have to do is change the required The code that operates the response is moved there!

    * Technically you can also use eval(), but eval() is evil for that purpose


    How to put callers on hold?

    You may currently have some code similar to this;

    function getWidthOfImage(src) {
        var outerScopeVar;
    
        var img = document.createElement('img');
        img.onload = function() {
            outerScopeVar = this.width;
        };
        img.src = src;
        return outerScopeVar;
    }
    
    var width = getWidthOfImage('lolcat.png');
    alert(width);

    However, we now know that returning outerScopeVar happens immediately; before the onload callback function updates the variable. This causes getWidthOfImage() to return undefined and raise the alert undefined.

    To fix this, we need to allow the function calling getWidthOfImage() to register a callback and then move the width alert inside that callback;

    function getWidthOfImage(src, cb) {     
        var img = document.createElement('img');
        img.onload = function() {
            cb(this.width);
        };
        img.src = src;
    }
    
    getWidthOfImage('lolcat.png', function (width) {
        alert(width);
    });

    ...As before, note that we have been able to remove the global variable (width in this case).

    reply
    0
  • P粉191610580

    P粉1916105802023-08-29 10:42:58

    One word answer: Asynchronicity.

    Preface

    This topic has been repeated at least thousands of times on Stack Overflow. So first I'd like to point out some very helpful resources:


    Answers to Current Questions

    Let's trace common behaviors first. In all examples, outerScopeVar is modified inside the function. The function is obviously not executed immediately; it is assigned or passed as a parameter. This is what we call a callback.

    The question now is, when will this callback be called?

    It depends on the specific situation. Let's try tracing some common behaviors again:

    • img.onload Might be called at some time in the future (if) the image has loaded successfully.
    • setTimeout may be called at some time in the future after the delay has expired and the timeout has not been clearTimeout canceled. Note: Even when using 0 as the delay, all browsers have an upper limit on the minimum timeout delay (specified as 4 milliseconds in the HTML5 specification).
    • When (and if) the Ajax request has completed successfully, At some point in the future the callback for jQuery $.post may be called.
    • Node.js's fs.readFile may be called at some time in the future when the file has been successfully read or an error has been thrown.

    In all cases, we have a callback that may be run at some time in the future. This "sometime in the future" is what we call Asynchronous Stream.

    Asynchronous execution is pushed out of the synchronous process. That is, while the synchronous code stack is executing, the asynchronous code will forever execute. This is what JavaScript is single-threaded about.

    More specifically, when the JS engine is idle - not executing a bunch of (a)synchronous code - it will poll for events that might trigger an asynchronous callback (e.g. timeout, network response received) and execute them one after another . This is considered an event loop.

    That is, the asynchronous code highlighted in the hand-drawn red shape can only execute after all remaining synchronous code in its respective code block has executed:

    In short, callback functions are created synchronously but executed asynchronously. You can't rely on the execution of an async function until you know it has executed, how do you do this?

    It's really simple. Logic that relies on the execution of an async function should be initiated/called from within that async function. For example, moving alert and console.log inside the callback function will output the expected results because the results are available at that time.

    Implement your own callback logic

    Often, you need to perform more operations on the result of an async function, or perform different operations on the result depending on where the async function is called. Let's deal with a more complex example:

    var outerScopeVar;
    helloCatAsync();
    alert(outerScopeVar);
    
    function helloCatAsync() {
        setTimeout(function() {
            outerScopeVar = 'Nya';
        }, Math.random() * 2000);
    }

    Note: I'm using setTimeout with a random delay as a generic async function; the same example works for Ajax, readFile, onload and any other asynchronous flow.

    This example obviously has the same problem as the other examples; it doesn't wait until the async function executes.

    Let's solve this problem by implementing our own callback system. First, we get rid of that ugly outerScopeVar which is completely useless in this case. Then we add a parameter that accepts a function argument, our callback. When the asynchronous operation completes, we call this callback and pass the result. Implementation (please read the comments in order):

    // 1. Call helloCatAsync passing a callback function,
    //    which will be called receiving the result from the async operation
    helloCatAsync(function(result) {
        // 5. Received the result from the async function,
        //    now do whatever you want with it:
        alert(result);
    });
    
    // 2. The "callback" parameter is a reference to the function which
    //    was passed as an argument from the helloCatAsync call
    function helloCatAsync(callback) {
        // 3. Start async operation:
        setTimeout(function() {
            // 4. Finished async operation,
            //    call the callback, passing the result as an argument
            callback('Nya');
        }, Math.random() * 2000);
    }

    Code snippet for the above example:

    // 1. Call helloCatAsync passing a callback function,
    //    which will be called receiving the result from the async operation
    console.log("1. function called...")
    helloCatAsync(function(result) {
        // 5. Received the result from the async function,
        //    now do whatever you want with it:
        console.log("5. result is: ", result);
    });
    
    // 2. The "callback" parameter is a reference to the function which
    //    was passed as an argument from the helloCatAsync call
    function helloCatAsync(callback) {
        console.log("2. callback here is the function passed as argument above...")
        // 3. Start async operation:
        setTimeout(function() {
        console.log("3. start async operation...")
        console.log("4. finished async operation, calling the callback, passing the result...")
            // 4. Finished async operation,
            //    call the callback passing the result as argument
            callback('Nya');
        }, Math.random() * 2000);
    }

    reply
    0
  • Cancelreply