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 .(在所有示例中, outerScopeVar
在function内部进行修改。) 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.(例如,将alert
和console.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, readFile
, onload
和任何其他异步流。)
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