在函数内部修改变量后,为什么变量未更改? -异步代码参考

本文探讨了在JavaScript中,为什么在函数内部修改的变量在外部保持不变,这是由于异步执行的特性。文章通过多个示例解释了回调、Promise和异步操作的基本原理,强调了异步代码如何在同步执行流之外运行。解决方案在于正确理解和使用回调函数、Promise或async/await来管理异步操作的结果。
摘要由CSDN通过智能技术生成

本文翻译自: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... 异步功能就像...

异步调用咖啡

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值