Introductioncallbacks
JavaScript Introduction:callbacks
Most of the JavaScript actions are asynchronous. To be more precise, you initiate them now, but they finish later. For example, actions like that may happen when you send requests to an API and you should wait to receive the response. In such cases, the other parts of the program will not wait for the response and so prevent the page from being blocked. A simple example of asynchronous functions is the setTimeout which takes two arguments. The first argument is the time (in milliseconds) to wait and the second argument is a function to execute after the time is passed. The second argument is a “callback function”. It is a function passed to another function, and it will be executed just “after” a process (waiting in this example) is done.
For a better understanding, let’s consider the following example:
function loadScript(src) {
//creates the <script> tag and adds it to the page, after which
// the script with the given src starts loading and starts after completion
(//具有给定src的脚本开始加载并在完成后启动)
let createScript = document.createElement('script');
createScript.src = src;
document.head.append(creatScript);
}
In the case above, you can see the loadScript(src) function loading a script with given src. (在上面的示例中,您可以看到loadScript (src)函数使用给定的src加载脚本。)
You can also use the following function:
// load and execute the script at the given path
loadScript('/path/script.js');
You can notice that the action is invoked asynchronously: it starts loading now but runs later when the function is finished.
In case there is a code below loadScript(…), it may not wait till the script loading ends:
loadScript('/path/script.js');
// the code below loadScript
// doesn't wait for the script loading ends
Now, imagine that you want to use the new script as soon as it loads. It is declaring new functions, and you wish to run them. But note that if you do it immediately after the loadScript(…), it will not work. It is demonstrated in the example below:
loadScript('/path/script.js'); // the script has "function newFunction() {…}"
newFunction(); // no such function!
The browser doesn’t have time for loading the script. So, the script loads and runs eventually. That’s all. But you must know when it takes place to apply new functions and variables of that script. (浏览器没有时间加载脚本。因此,脚本最终会加载并运行。仅此而已。但您必须知道何时应用该脚本的新函数和变量。)
We recommend you to add a callback function in the place of the second argument to the loadScript, which should execute when the script loads. (我们建议您在loadScript的第二个参数位置添加一个回调函数,该函数应在脚本加载时执行。)
For instance:
function loadScript(src, callback) {
let createScript = document.createElement('script');
createScript.src = src;
createScript.onload = () => callback(createScript);
document.head.append(createScript);
}
In case you intend to call new functions from the script, you should write it in the callback, as follows:
loadScript('/path/script.js', function () {
// the callback runs after the script is loaded
(//加载脚本后运行回调)
newFunction();now it works
});
The second argument is considered a function, which runs after the action is completed:
function loadScript(src, callback) {
let cScript = document.createElement('script');
cScript.src = src;
cScript.onload = () => callback(cScript);
document.head.append(cScript);
}
loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', cScript => {
console.log(`Cool, the script ${cScript.src} is loaded`);
console.log(_); // function declared in the loaded script
});
It is known as a “callback-based” style of asynchronous programming. A function, doing something asynchronously, needs to provide a callback argument, in which you put the function to run after its completion. (它被称为“基于回调”的异步编程风格。以异步方式执行某些操作的函数需要提供一个回调参数,在该参数中,需要在函数完成后运行该函数。)
Please, note that it’s a general approach. (请注意,这是一种通用的方法。)
Callback Inside Callback
Callback Inside Callback (回拨内部回拨)
The next question is how to load two scripts sequentially. (下一个问题是如何顺序加载两个脚本。)
Its natural solution is putting the second loadScript call inside the callback, as follows:
loadScript('/path/script.js', function (script) {
console.log(`Cool, the ${script.src} is loaded, let's load one more`);
loadScript('/path/script2.js', function (script) {
console.log(`Cool, the second script is loaded`);
});
});
After the completion of the outer loadScript, the callback can initiate the inner one. But, you may need another script:
loadScript('/path/script.js', function (script) {
loadScript('/path/script2.js', function (script) {
loadScript('/path/script3.js', function (script) {
// ...continue after all scripts are loaded
(//...加载所有脚本后继续)
});
})
});
So, it can be assumed that each new action is inside a callback. It is considered suitable for a few actions, but bad for many. (因此,可以假设每个新操作都在回调中。它被认为适合一些动作,但对许多人来说是不好的。)
Handling Errors
Handling Errors (处理错误)
In the examples above, errors were not considered. (在上述示例中,未考虑错误。)
Let’s check out an enhanced version of the loadScript, which tracks loading errors. It looks like this:
function loadScript(src, callback) {
let createScript = document.createElement('script');
createScript.src = src;
createScript.onload = () => callback(null, createScript);
createScript.onerror = () => callback(new Error(`Script load error for ${src}`));
document.head.append(createScript);
}
It may call callback(null, script) for a successful load: otherwise there is a callback(error).
Its usage is as follows:
loadScript('/path/script.js', function (error, script) {
if (error) {
// handle error
(//handle error)
} else {
// script loaded successfully
(脚本成功加载)
}
});
The solution, used for the loadScript is still actual. It’s known as “error-first callback” style. (用于loadScript的解决方案仍然是实际的。这被称为“错误优先回调”风格。)
The convention looks like this:
The first argument of the callback is kept for the possible error. The callback(err) is called after. (-为可能的错误保留回调的第一个参数。之后调用回调(err)。)
The second and the next coming arguments are for the successful result. After that, it is necessary to call callback(null, result1, result2…)). (-第二个和下一个参数是成功的结果。之后,需要调用callback (null, result1, result2…) )。)
We can conclude that a callback function can be called for reporting errors, as well as for passing back results. (我们可以得出结论,可以调用回调函数来报告错误以及传递结果。)
The Pyramid of Doom
The Pyramid of Doom (厄运金字塔)
For multiple asynchronous actions following one another, you can use the code below:
loadScript('script1.js', function (error, script) {
if (error) {
handleError(error);
} else {
// ...
(//...)
loadScript('script2.js', function (error, script) {
if (error) {
handleError(error);
} else {
// ...
(//...)
loadScript('script3.js', function (error, script) {
if (error) {
handleError(error);
} else {
// continue after loading all scripts
(//加载所有脚本后继续)
}
});
}
})
}
});
As you see, the calls become more nested. Along with them, the code becomes more profound and more challenging to manage, particularly when there is a real code instead of … it may contain more loops, conditional statements, and more. (如您所见,调用变得更加嵌套。与他们一起,代码变得更加深刻和更具挑战性的管理,特别是当有一个真正的代码,而不是……它可能包含更多的循环,条件语句等。)
This pyramid of nested calls grows to the right with each of the synchronous actions. Finally, it gets out of control. (这个嵌套调用的金字塔随着每个同步操作向右生长。最后,它失控了。)
It is possible to alleviate such a problem by transforming every action into a standalone function, as follows:
loadScript('script1.js', step1);
function step1(error, script) {
if (error) {
handleError(error);
} else {
// ...
(//...)
loadScript('script2.js', step2);
}
}
function step2(error, script) {
if (error) {
handleError(error);
} else {
// ...
(//...)
loadScript('script3.js', step3);
}
}
function step3(error, script) {
if (error) {
handleError(error);
} else {
// continue after loading all scripts
(//加载所有脚本后继续)
}
};
That’s it. It does the same without deep nesting. (就是这样。它在没有深度嵌套的情况下也会这样做。)
There is an additional solution to this problem, which is using JavaScript promise. You can learn about it in the next chapter. (此问题还有另一个解决方案,即使用JavaScript promise。您可以在下一章中了解到这一点。)