Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
464 views
in Technique[技术] by (71.8m points)

javascript - 在函数内部修改变量后,为什么变量未更改? -异步代码参考(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.(随时改进此问题,并添加更多简化的示例,社区可以识别。)
  ask by Fabrício Matté translate from so

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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 <a href="https://stackoom.com/link/aHR0cHM6Ly9kZXZlbG9wZXIubW96aWxsYS5vcmcvZW4tVVMvZG9jcy9XZWIvSmF2YVNjcmlwdC9SZWZlcmVuY2UvR2xvYmFsX09iamVjdHMvUHJvbWlzZQ==" rel="nofollow noopener" target="_blan


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

57.0k users

...