1. 介绍:回调
JavaScript中的许多操作都是异步的。
例如,看看这个函数loadScript(src):
|
|
该函数的目的是加载一个新的脚本。当它添加<script src="…">
到文档时,浏览器会加载并执行它。
我们可以像这样使用它:
|
|
该函数被称为“异步”,因为动作(脚本加载)现在不会完成,但稍后会完成。
该调用启动脚本加载,然后继续执行。在加载脚本时,下面的代码可能会完成执行,如果加载需要时间,其他脚本也可能同时运行。
|
|
现在让我们假设我们想在加载时使用新脚本。它可能会声明新的函数,所以我们想运行它们。
但是如果我们在loadScript(…)运行结束后立即这样做,那是行不通的。
|
|
自然,浏览器可能没有时间加载脚本。所以立即调用新函数失败。截至目前,loadScript函数没有提供跟踪加载完成的方法。脚本加载并最终运行,就这些了。但是我们想知道它何时发生,从该脚本中使用新的函数和变量。
让我们添加一个callback函数作为第二个参数,loadScript以便在脚本加载时执行:
|
|
现在,如果我们想从脚本调用新的函数,我们应该在回调中写入:
|
|
这就是一种方式:第二个参数是一个在完成脚本时运行的函数(通常是匿名的)。
下面是一个真实脚本的可运行示例:
|
|
这就是所谓的“基于回调”的异步编程风格。一个异步执行某个功能的函数应该提供一个callback参数,我们可以在该函数完成后让它运行。
我们在这里做了loadScript,但当然这是一个普遍的方法。
2. 回调中的回调
如何顺序加载两个脚本:先执行第一个,然后是第二个脚本?
自然的解决方案是将第二个loadScript调用放在回调中,如下所示:
|
|
外部loadScript完成后,回调启动内部回调。
如果我们想要一个更多的脚本…怎么办?
|
|
所以,每一个新的动作都在回调中。对于少数脚本来说这很好,但对于多个脚本l来说并不友好,所以我们很快会看到其他的方式。
3. 处理错误
在上面的例子中,我们没有考虑错误。如果脚本加载失败怎么办?我们的回调应该能够对此作出反应。
以下loadScript是跟踪加载错误的改进版本:
|
|
它要求callback(null, script)成功加载,否则执行callback(error)。
用法:
|
|
我们再次使用的配方loadScript实际上很常见。它被称为“错误优先回调”风格。
该约定是:
callback如果发生错误,则保留第一个参数。然后callback(error)被调用。
第二个参数(如果需要,还有下一个参数)是为了获得成功的结果。然后callback(null, result1, result2…)被调用。
因此,单个callback函数既用于报告错误又用于传回结果。
4. 末日金字塔
从第一眼看来,上面是一种可行的异步编码方式。事实确实如此。对于一个或两个嵌套的调用看起来很好。
但是对于接连出现的多个异步操作,我们将拥有这样的代码:
|
|
在上面的代码中:
我们加载1.js,然后如果没有错误;我们加载2.js,然后如果没有错误;我们加载3.js,然后如果没有错误 … 做其他事情(*)。
随着调用变得更加嵌套,代码变得越来越深,越来越难以管理,特别是如果我们有真正的代码而不是…,这回导致可能包含更多循环,条件语句等等。
这有时被称为“回调地狱”。
每个异步操作都会使嵌套调用的“金字塔”向右扩展。很快它就失去了控制。
所以这种编码方式不是很好。
我们可以尝试通过使每个动作成为独立的功能来缓解问题,如下所示:
|
|
看到没有?它也是如此,现在没有深厚的嵌套,因为我们将每个脚本都作为一个独立的顶层函数。
上面的代码也能正常运行,但是代码看起来像一个撕裂的电子表格。这很难阅读,你可能已经注意到了。人们在阅读时需要在各个部分之间跳跃。这很不方便,特别是读者不熟悉代码并且不知道在何处跳跃。
此外,命名的功能step*都是一次性使用,它们只是为了避免“回调地狱”而创建。没有人会在该脚本之外重复使用它们。所以这里有一些名称空间混乱。
我们希望有更好的东西。
幸运的是,还有其他方法可以避免这种“回调地狱”。最好的方法之一是使用“promises”。
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
5. 写在最后
本篇是翻译!
本篇是翻译!
本篇是翻译!
如有错误,欢迎指出!
想看更完整的内容,可点击 => 原文地址: