在函數內部修改變量後,爲什麼變量未更改? -異步代碼參考

本文翻譯自:Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference

Given the following examples, why is outerScopeVar undefined in all cases? 給定以下示例,爲什麼在所有情況下都未定義outerScopeVar

var outerScopeVar;

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

var outerScopeVar;
setTimeout(function() {
    outerScopeVar = 'Hello Asynchronous World!';
}, 0);
alert(outerScopeVar);

// Example using some jQuery
var outerScopeVar;
$.post('loldog', function(response) {
    outerScopeVar = response;
});
alert(outerScopeVar);

// Node.js example
var outerScopeVar;
fs.readFile('./catdog.html', function(err, data) {
    outerScopeVar = data;
});
console.log(outerScopeVar);

// with promises
var outerScopeVar;
myPromise.then(function (response) {
    outerScopeVar = response;
});
console.log(outerScopeVar);

// geolocation API
var outerScopeVar;
navigator.geolocation.getCurrentPosition(function (pos) {
    outerScopeVar = pos;
});
console.log(outerScopeVar);

Why does it output undefined in all of these examples? 爲什麼在所有這些示例中都輸出undefined I don't want workarounds, I want to know why this is happening. 我不想要解決方法,我想知道爲什麼會這樣。


Note: This is a canonical question for JavaScript asynchronicity . 注意:這是JavaScript異步性的典型問題。 Feel free to improve this question and add more simplified examples which the community can identify with. 隨時改進此問題,並添加更多簡化的示例,社區可以識別。


#1樓

參考:https://stackoom.com/question/1bItC/在函數內部修改變量後-爲什麼變量未更改-異步代碼參考


#2樓

One word answer: asynchronicity . 一句話回答: 異步性

Forewords 前言

This topic has been iterated at least a couple of thousands of times, here, in Stack Overflow. 在Stack Overflow中,該主題已至少迭代了數千次。 Hence, first off I'd like to point out some extremely useful resources: 因此,首先,我想指出一些非常有用的資源:


The answer to the question at hand 眼前問題的答案

Let's trace the common behavior first. 讓我們首先跟蹤常見行爲。 In all examples, the outerScopeVar is modified inside of a function . 在所有示例中, outerScopeVarfunction內部進行修改。 That function is clearly not executed immediately, it is being assigned or passed as an argument. 該函數顯然不會立即執行,而是被分配或作爲參數傳遞。 That is what we call a callback . 這就是我們所說的回調

Now the question is, when is that callback called? 現在的問題是,何時調用該回調?

It depends on the case. 這要視情況而定。 Let's try to trace some common behavior again: 讓我們嘗試再次跟蹤一些常見行爲:

  • img.onload may be called sometime in the future , when (and if) the image has successfully loaded. img.onload可能在將來的某個時間(如果(如果))圖像成功加載img.onload調用。
  • setTimeout may be called sometime in the future , after the delay has expired and the timeout hasn't been canceled by clearTimeout . setTimeout可能會在延遲到期後並且clearTimeout尚未取消超時之後的將來某個時間調用。 Note: even when using 0 as delay, all browsers have a minimum timeout delay cap (specified to be 4ms in the HTML5 spec). 注意:即使將0用作延遲,所有瀏覽器都具有最小超時延遲上限(在HTML5規範中指定爲4ms)。
  • jQuery $.post 's callback may be called sometime in the future , when (and if) the Ajax request has been completed successfully. jQuery $.post的回調可能在將來的某個時間(當Ajax請求已成功完成時)被調用。
  • Node.js's fs.readFile may be called sometime in the future , when the file has been read successfully or thrown an error. 當文件已被成功讀取或引發錯誤時, 將來可能會調用Node.js的fs.readFile

In all cases, we have a callback which may run sometime in the future . 在所有情況下,我們都有一個回調,它可能在將來的某個時間運行。 This "sometime in the future" is what we refer to as asynchronous flow . 這種“將來的某個時候”就是我們所說的異步流

Asynchronous execution is pushed out of the synchronous flow. 異步執行從同步流中推出。 That is, the asynchronous code will never execute while the synchronous code stack is executing. 也就是說,異步代碼將永遠不會在同步代碼堆棧執行時執行。 This is the meaning of JavaScript being single-threaded. 這就是JavaScript是單線程的意思。

More specifically, when the JS engine is idle -- not executing a stack of (a)synchronous code -- it will poll for events that may have triggered asynchronous callbacks (eg expired timeout, received network response) and execute them one after another. 更具體地說,當JS引擎處於空閒狀態時-不執行(a)同步代碼的堆棧-它將輪詢可能觸發異步回調的事件(例如,過期的超時,收到的網絡響應),然後依次執行它們。 This is regarded as Event Loop . 這被視爲事件循環

That is, the asynchronous code highlighted in the hand-drawn red shapes may execute only after all the remaining synchronous code in their respective code blocks have executed: 也就是說,以手繪紅色形狀突出顯示的異步代碼只有在其各自代碼塊中的所有其餘同步代碼都已執行後才能執行:

異步代碼突出顯示

In short, the callback functions are created synchronously but executed asynchronously. 簡而言之,回調函數是同步創建的,但異步執行。 You just can't rely on the execution of an asynchronous function until you know it has executed, and how to do that? 在知道異步函數已執行之前,您就不能依賴它的執行,以及如何執行?

It is simple, really. 真的很簡單。 The logic that depends on the asynchronous function execution should be started/called from inside this asynchronous function. 應從該異步函數內部啓動/調用依賴於異步函數執行的邏輯。 For example, moving the alert s and console.log s too inside the callback function would output the expected result, because the result is available at that point. 例如,將alertconsole.log移到回調函數中也將輸出預期結果,因爲此時該結果可用。

Implementing your own callback logic 實現自己的回調邏輯

Often you need to do more things with the result from an asynchronous function or do different things with the result depending on where the asynchronous function has been called. 通常,您需要根據異步函數的結果執行更多操作,或者根據調用異步函數的位置對結果執行不同的操作。 Let's tackle a bit 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 asynchronous function, the same example applies to Ajax, readFile , onload and any other asynchronous flow. 注意:我使用具有隨機延遲的setTimeout作爲通用異步函數,同一示例適用於Ajax, readFileonload和任何其他異步流。

This example clearly suffers from the same issue as the other examples, it is not waiting until the asynchronous function executes. 顯然,該示例與其他示例存在相同的問題,它不等待異步函數執行。

Let's tackle it implementing a callback system of our own. 讓我們解決實現自己的回調系統的問題。 First off, we get rid of that ugly outerScopeVar which is completely useless in this case. 首先,我們擺脫了醜陋的outerScopeVar ,它在這種情況下是完全沒有用的。 Then we add a parameter which accepts a function argument, our callback. 然後,我們添加一個接受函數參數的參數,即回調。 When the asynchronous operation finishes, we call this callback passing the result. 當異步操作完成時,我們調用此回調傳遞結果。 The 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 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 argument
        callback('Nya');
    }, Math.random() * 2000);
}

Code snippet of 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 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); } 

Most often in real use cases, the DOM API and most libraries already provide the callback functionality (the helloCatAsync implementation in this demonstrative example). 在實際使用案例中,大多數情況下,DOM API和大多數庫已經提供了回調功能(此演示示例中的helloCatAsync實現)。 You only need to pass the callback function and understand that it will execute out of the synchronous flow, and restructure your code to accommodate for that. 您只需要傳遞迴調函數,並瞭解它將在同步流之外執行,並重新組織代碼以適應該情況。

You will also notice that due to the asynchronous nature, it is impossible to return a value from an asynchronous flow back to the synchronous flow where the callback was defined, as the asynchronous callbacks are executed long after the synchronous code has already finished executing. 您還會注意到,由於異步特性,它是不可能return從異步流回到這裏被定義回調同步流量的值,作爲後同步碼已執行完畢,異步回調長期執行。

Instead of return ing a value from an asynchronous callback, you will have to make use of the callback pattern, or... Promises. 而不是從異步回調中return值,您將不得不使用回調模式,或者。。。

Promises 承諾

Although there are ways to keep the callback hell at bay with vanilla JS, promises are growing in popularity and are currently being standardized in ES6 (see Promise - MDN ). 儘管可以通過香草JS來阻止回調地獄 ,但是Promise越來越流行,並且目前已在ES6中標準化(請參閱Promise-MDN )。

Promises (aka Futures) provide a more linear, and thus pleasant, reading of the asynchronous code, but explaining their entire functionality is out of the scope of this question. Promises(又名Futures)提供了一種更加線性,從而令人愉悅的異步代碼閱讀方式,但是解釋其整個功能不在此問題的範圍之內。 Instead, I'll leave these excellent resources for the interested: 相反,我會將這些出色的資源留給感興趣的人:


More reading material about JavaScript asynchronicity 有關JavaScript異步性的更多閱讀材料


Note: I've marked this answer as Community Wiki, hence anyone with at least 100 reputations can edit and improve it! 注意:我已將此答案標記爲Community Wiki,因此具有至少100個信譽的任何人都可以對其進行編輯和改進! Please feel free to improve this answer, or submit a completely new answer if you'd like as well. 請隨時改進此答案,或者也可以提交一個全新的答案。

I want to turn this question into a canonical topic to answer asynchronicity issues which are unrelated to Ajax (there is How to return the response from an AJAX call? for that), hence this topic needs your help to be as good and helpful as possible! 我想將這個問題變成一個規範的主題,以回答與Ajax不相關的異步性問題( 爲此,有一種如何從AJAX調用返回響應的方法 ),因此,這個主題需要您的幫助,以使其儘可能好和有用。 !


#3樓

Fabrício's answer is spot on; Fabrício的答案就在現場。 but I wanted to complement his answer with something less technical, which focusses on an analogy to help explain the concept of asynchronicity . 但是我想用一種不太技術性的東西來補充他的答案,該技術着重於類比,以幫助解釋異步性的概念


An Analogy... 比喻...

Yesterday, the work I was doing required some information from a colleague. 昨天,我正在做的工作需要同事提供一些信息。 I rang him up; 我給他打電話。 here's how the conversation went: 對話過程如下:

Me : Hi Bob, I need to know how we foo 'd the bar 'd last week. :鮑勃,您好,我需要知道上週我們如何愚弄 酒吧 Jim wants a report on it, and you're the only one who knows the details about it. 吉姆想要一份報告,而您是唯一知道此事細節的人。

Bob : Sure thing, but it'll take me around 30 minutes? 鮑勃 :好的,但是我要花30分鐘左右嗎?

Me : That's great Bob. :太好了,鮑勃。 Give me a ring back when you've got the information! 瞭解信息後給我回電話!

At this point, I hung up the phone. 此時,我掛了電話。 Since I needed information from Bob to complete my report, I left the report and went for a coffee instead, then I caught up on some email. 由於我需要鮑勃提供的信息來完成我的報告,因此我離開了報告,去喝咖啡,然後接了一封電子郵件。 40 minutes later (Bob is slow), Bob called back and gave me the information I needed. 40分鐘後(鮑勃很慢),鮑勃回電話給了我我需要的信息。 At this point, I resumed my work with my report, as I had all the information I needed. 至此,我有了我需要的所有信息,就可以繼續處理報告了。


Imagine if the conversation had gone like this instead; 想象一下,如果談話像這樣進行了;

Me : Hi Bob, I need to know how we foo 'd the bar 'd last week. :鮑勃,您好,我需要知道上週我們如何愚弄 酒吧 Jim want's a report on it, and you're the only one who knows the details about it. 吉姆想要一份關於它的報告,而您是唯一知道有關它的細節的報告。

Bob : Sure thing, but it'll take me around 30 minutes? 鮑勃 :好的,但是我要花30分鐘左右嗎?

Me : That's great Bob. :太好了,鮑勃。 I'll wait. 我會等。

And I sat there and waited. 我坐在那裏等。 And waited. 並等待。 And waited. 並等待。 For 40 minutes. 40分鐘。 Doing nothing but waiting. 除了等待,什麼都不做。 Eventually, Bob gave me the information, we hung up, and I completed my report. 最終,鮑勃給了我信息,我們掛斷了電話,我完成了報告。 But I'd lost 40 minutes of productivity. 但是我失去了40分鐘的生產力。


This is asynchronous vs. synchronous behavior 這是異步還是同步行爲

This is exactly what is happening in all the examples in our question. 這正是我們問題中所有示例所發生的情況。 Loading an image, loading a file off disk, and requesting a page via AJAX are all slow operations (in the context of modern computing). 加載映像,從磁盤加載文件以及通過AJAX請求頁面都是緩慢的操作(在現代計算的背景下)。

Rather than waiting for these slow operations to complete, JavaScript lets you register a callback function which will be executed when the slow operation has completed. JavaScript無需等待這些慢速操作完成,而是讓您註冊將在慢速操作完成後執行的回調函數。 In the meantime, however, JavaScript will continue to execute other code. 同時,JavaScript將繼續執行其他代碼。 The fact that JavaScript executes other code whilst waiting for the slow operation to complete makes the behavior asynchronous . JavaScript在等待慢速操作完成的同時執行其他代碼的事實使行爲異步 Had JavaScript waited around for the operation to complete before executing any other code, this would have been synchronous behavior. 如果JavaScript在執行任何其他代碼之前等待操作完成,那將是同步行爲。

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 code above, we're asking JavaScript to load lolcat.png , which is a sloooow operation. 在上面的代碼中,我們要求JavaScript加載lolcat.png ,這是一個sloooow操作。 The callback function will be executed once this slow operation has done, but in the meantime, JavaScript will keep processing the next lines of code; 一旦完成此緩慢的操作,便會執行回調函數,但與此同時,JavaScript將繼續處理下一行代碼; ie alert(outerScopeVar) . alert(outerScopeVar)

This is why we see the alert showing undefined ; 這就是爲什麼我們看到警報顯示undefined since the alert() is processed immediately, rather than after the image has been loaded. 因爲alert()會立即處理,而不是在加載圖像後處理。

In order to fix our code, all we have to do is move the alert(outerScopeVar) code into the callback function. 爲了修復我們的代碼,我們要做的就是將alert(outerScopeVar)代碼移到回調函數中。 As a consequence of this, we no longer need the outerScopeVar variable declared as a global variable. 因此,我們不再需要將outerScopeVar變量聲明爲全局變量。

var img = document.createElement('img');

img.onload = function() {
    var localScopeVar = this.width;
    alert(localScopeVar);
};

img.src = 'lolcat.png';

You'll always see a callback is specified as a function, because that's the only* way in JavaScript to define some code, but not execute it until later. 您將始終看到將回調指定爲函數,因爲這是JavaScript中定義某些代碼的唯一*方式,但要等到以後再執行。

Therefore, in all of our examples, the function() { /* Do something */ } is the callback; 因此,在我們所有的示例中, function() { /* Do something */ }是回調; to fix all the examples, all we have to do is move the code which needs the response of the operation into there! 要修復所有示例,我們要做的就是將需要操作響應的代碼移到其中!

* Technically you can use eval() as well, but eval() is evil for this purpose *從技術上講,您也可以使用eval() ,但是eval()對此是有害


How do I keep my caller waiting? 如何讓來電者等待?

You might 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 the return outerScopeVar happens immediately; 但是,我們現在知道return outerScopeVar立即發生。 before the onload callback function has updated the variable. onload回調函數更新變量之前。 This leads to getWidthOfImage() returning undefined , and undefined being alerted. 這將導致getWidthOfImage()返回undefined ,並且將向undefined發出警報。

To fix this, we need to allow the function calling getWidthOfImage() to register a callback, then move the alert'ing of the width to be within that callback; 爲了解決這個問題,我們需要允許調用getWidthOfImage()的函數註冊一個回調,然後將寬度的警報移到該回調內;

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've been able to remove the global variables (in this case width ). ...與以前一樣,請注意,我們已經能夠刪除全局變量(在本例中爲width )。


#4樓

Here's a more concise answer for people that are looking for a quick reference as well as some examples using promises and async/await. 對於正在尋求快速參考的人們,以及使用promise和async / await的一些示例,這是一個更簡潔的答案。

Start with the naive approach (that doesn't work) for a function that calls an asynchronous method (in this case setTimeout ) and returns a message: 從針對調用異步方法(在本例中爲setTimeout )並返回消息的函數的幼稚方法(不起作用)開始:

function getMessage() {
  var outerScopeVar;
  setTimeout(function() {
    outerScopeVar = 'Hello asynchronous world!';
  }, 0);
  return outerScopeVar;
}
console.log(getMessage());

undefined gets logged in this case because getMessage returns before the setTimeout callback is called and updates outerScopeVar . 在這種情況下,將記錄undefined ,因爲getMessage在調用setTimeout回調之前返回並更新outerScopeVar

The two main ways to solve it are using callbacks and promises : 解決它的兩種主要方法是使用回調Promise

Callbacks 回呼

The change here is that getMessage accepts a callback parameter that will be called to deliver the results back to the calling code once available. 此處的更改是getMessage接受一個callback參數,該參數將被調用以將結果傳遞迴調用代碼(一旦可用)。

function getMessage(callback) {
  setTimeout(function() {
    callback('Hello asynchronous world!');
  }, 0);
}
getMessage(function(message) {
  console.log(message);
});

Promises 承諾

Promises provide an alternative which is more flexible than callbacks because they can be naturally combined to coordinate multiple async operations. Promise提供了一種比回調更靈活的替代方法,因爲它們可以自然地組合在一起以協調多個異步操作。 A Promises/A+ standard implementation is natively provided in node.js (0.12+) and many current browsers, but is also implemented in libraries like Bluebird and Q . Promises / A +標準實現是在node.js(0.12+)和許多當前的瀏覽器中本地提供的,但也可以在BluebirdQ之類的庫中實現。

function getMessage() {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      resolve('Hello asynchronous world!');
    }, 0);
  });
}

getMessage().then(function(message) {
  console.log(message);  
});

jQuery Deferreds jQuery Deferreds

jQuery provides functionality that's similar to promises with its Deferreds. jQuery提供的功能類似於其Deferred的Promise。

function getMessage() {
  var deferred = $.Deferred();
  setTimeout(function() {
    deferred.resolve('Hello asynchronous world!');
  }, 0);
  return deferred.promise();
}

getMessage().done(function(message) {
  console.log(message);  
});

async/await 異步/等待

If your JavaScript environment includes support for async and await (like Node.js 7.6+), then you can use promises synchronously within async functions: 如果您的JavaScript環境包括對asyncawait支持(例如Node.js 7.6+),那麼您可以在async函數中同步使用promises:

function getMessage () {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve('Hello asynchronous world!');
        }, 0);
    });
}

async function main() {
    let message = await getMessage();
    console.log(message);
}

main();

#5樓

In all these scenarios outerScopeVar is modified or assigned a value asynchronously or happening in a later time(waiting or listening for some event to occur),for which the current execution will not wait .So all these cases current execution flow results in outerScopeVar = undefined 在所有這些情況下, outerScopeVar異步修改或分配一個值,或者在稍後發生(等待或偵聽某個事件發生)時發生,當前執行不會等待 。因此,所有這些情況下,當前執行流程會導致outerScopeVar = undefined

Let's discuss each examples(I marked the portion which is called asynchronously or delayed for some events to occur): 讓我們討論每個示例(我標記了異步或延遲某些事件發生的部分):

1. 1。

在此處輸入圖片說明

Here we register an eventlistner which will be executed upon that particular event.Here loading of image.Then the current execution continuous with next lines img.src = 'lolcat.png'; 在這裏我們註冊一個事件監聽器,該事件監聽器將在特定事件發生時執行。在這裏加載圖像。然後當前執行與下一行連續img.src = 'lolcat.png'; and alert(outerScopeVar); alert(outerScopeVar); meanwhile the event may not occur. 同時該事件可能不會發生。 ie, funtion img.onload wait for the referred image to load, asynchrously. 即,函數img.onload等待引用的圖像加載。 This will happen all the folowing example- the event may differ. 這將在以下所有示例中發生-事件可能有所不同。

2. 2。

2

Here the timeout event plays the role, which will invoke the handler after the specified time. 此處,超時事件起着作用,它將在指定時間後調用處理程序。 Here it is 0 , but still it registers an asynchronous event it will be added to the last position of the Event Queue for execution, which makes the guaranteed delay. 在這裏它是0 ,但是它仍然註冊一個異步事件,它將被添加到Event Queue的最後一個位置以便執行,從而保證了延遲。

3. 3。

在此處輸入圖片說明 This time ajax callback. 這次是ajax回調。

4. 4。

在此處輸入圖片說明

Node can be consider as a king of asynchronous coding.Here the marked function is registered as a callback handler which will be executed after reading the specified file. 可以將Node視爲異步編碼之王,這裏標記的函數被註冊爲回調處理程序,將在讀取指定文件後執行。

5. 5,

在此處輸入圖片說明

Obvious promise (something will be done in future) is asynchronous. 明顯的承諾(將來會做一些事情)是異步的。 see What are the differences between Deferred, Promise and Future in JavaScript? 請參閱JavaScript中的Deferred,Promise和Future之間有什麼區別?

https://www.quora.com/Whats-the-difference-between-a-promise-and-a-callback-in-Javascript https://www.quora.com/Whats-the-difference-between-a-promise-and-a-callback-in-Javascript


#6樓

To state the obvious, the cup represents outerScopeVar . 顯而易見,杯子代表outerScopeVar

Asynchronous functions be like... 異步功能就像...

異步調用咖啡

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章