JavaScript 异步编程与回调
🌐 JavaScript Asynchronous Programming and Callbacks
编程语言中的异步性
🌐 Asynchronicity in Programming Languages
计算机在设计上是异步的。
🌐 Computers are asynchronous by design.
异步意味着事情可以独立于主程序流发生。
🌐 Asynchronous means that things can happen independently of the main program flow.
在当前的消费类计算机中,每个程序都会运行特定的时间段,然后停止执行,以便让另一个程序继续执行。这个过程以极快的速度循环运行,以至于我们无法察觉。我们认为计算机可以同时运行多个程序,但这只是一种错觉(多处理器机器除外)。
🌐 In the current consumer computers, every program runs for a specific time slot and then it stops its execution to let another program continue their execution. This thing runs in a cycle so fast that it's impossible to notice. We think our computers run many programs simultaneously, but this is an illusion (except on multiprocessor machines).
程序在内部使用 _ 中断 _,这是一种发送给处理器的信号,用于引起系统的注意。
🌐 Programs internally use interrupts, a signal that's emitted to the processor to gain the attention of the system.
我们现在不深入探讨内部细节,但请记住,程序是异步的并且在需要处理时才会暂停执行,这是正常的,这样计算机就可以在此期间执行其他操作。当程序在等待网络响应时,它不能在请求完成之前暂停处理器。
🌐 Let's not go into the internals of this now, but just keep in mind that it's normal for programs to be asynchronous and halt their execution until they need attention, allowing the computer to execute other things in the meantime. When a program is waiting for a response from the network, it cannot halt the processor until the request finishes.
通常,编程语言是同步的,有些语言通过语言本身或库提供管理异步的方法。C、Java、C#、PHP、Go、Ruby、Swift 和 Python 默认都是同步的。其中一些通过使用线程或生成新进程来处理异步操作。
🌐 Normally, programming languages are synchronous and some provide a way to manage asynchronicity in the language or through libraries. C, Java, C#, PHP, Go, Ruby, Swift, and Python are all synchronous by default. Some of them handle async operations by using threads, spawning a new process.
JavaScript
JavaScript 默认是同步的,并且是单线程的。这意味着代码不能创建新的线程并行运行。
🌐 JavaScript is synchronous by default and is single threaded. This means that code cannot create new threads and run in parallel.
代码行按顺序执行,一个接一个,例如:
🌐 Lines of code are executed in series, one after another, for example:
const = 1;
const = 2;
const = * ;
.();
doSomething();
但 JavaScript 最初诞生于浏览器中,它一开始的主要工作是响应用户的操作,比如 onClick、onMouseOver、onChange、onSubmit 等等。它如何在同步编程模型下做到这一点呢?
🌐 But JavaScript was born inside the browser, its main job, in the beginning, was to respond to user actions, like onClick, onMouseOver, onChange, onSubmit and so on. How could it do this with a synchronous programming model?
答案就在它的环境中。浏览器通过提供一组可以处理这种功能的 API 来实现这一点。
🌐 The answer was in its environment. The browser provides a way to do it by providing a set of APIs that can handle this kind of functionality.
最近,Node.js 引入了一个非阻塞 I/O 环境,将这个概念扩展到文件访问、网络调用等。
🌐 More recently, Node.js introduced a non-blocking I/O environment to extend this concept to file access, network calls and so on.
回调
🌐 Callbacks
你无法知道用户什么时候会点击按钮。因此,你为点击事件定义一个事件处理程序。这个事件处理程序接受一个函数,当事件被触发时,这个函数将被调用:
🌐 You can't know when a user is going to click a button. So, you define an event handler for the click event. This event handler accepts a function, which will be called when the event is triggered:
.('button').('click', () => {
// item clicked
});
这就是所谓的回调。
🌐 This is the so-called callback.
回调是一个简单的函数,它作为值传递给另一个函数,并且只有在事件发生时才会执行。我们可以这样做是因为 JavaScript 拥有一等函数,可以被赋值给变量并传递给其他函数(称为高阶函数)。
🌐 A callback is a simple function that's passed as a value to another function, and will only be executed when the event happens. We can do this because JavaScript has first-class functions, which can be assigned to variables and passed around to other functions (called higher-order functions)
通常会将所有客户端代码封装在 window 对象上的 load 事件监听器中,该函数只有在页面准备好后才会运行回调函数:
🌐 It's common to wrap all your client code in a load event listener on the window object, which runs the callback function only when the page is ready:
.('load', () => {
// window loaded
// do what you want
});
回调无处不在,而不仅仅是在 DOM 事件中。
🌐 Callbacks are used everywhere, not just in DOM events.
一个常见的例子是使用定时器:
🌐 One common example is by using timers:
(() => {
// runs after 2 seconds
}, 2000);
XHR 请求也接受回调,在此示例中,通过将函数分配给将在发生特定事件时调用的属性(在本例中,请求的状态发生变化):
🌐 XHR requests also accept a callback, in this example by assigning a function to a property that will be called when a particular event occurs (in this case, the state of the request changes):
const = new ();
. = () => {
if (. === 4) {
if (. === 200) {
.(.);
} else {
.('error');
}
}
};
.('GET', 'https://yoursite.com');
.();
在回调中处理错误
🌐 Handling errors in callbacks
你如何处理回调中的错误?一个非常常见的策略是使用 Node.js 采用的方法:任何回调函数的第一个参数都是错误对象:错误优先回调
🌐 How do you handle errors with callbacks? One very common strategy is to use what Node.js adopted: the first parameter in any callback function is the error object: error-first callbacks
如果没有错误,对象是 null。如果有错误,它将包含一些错误描述和其他信息。
🌐 If there is no error, the object is null. If there is an error, it contains some description of the error and other information.
const = ('node:fs');
.('/file.json', (, ) => {
if () {
// handle error
.();
return;
}
// no errors, process data
.();
});
回调的问题
🌐 The problem with callbacks
回调非常适合简单的情况!
🌐 Callbacks are great for simple cases!
但是每个回调都会增加一层嵌套,当你有大量回调时,代码很快就会变得复杂:
🌐 However every callback adds a level of nesting, and when you have lots of callbacks, the code starts to be complicated very quickly:
.('load', () => {
.('button').('click', () => {
(() => {
items.forEach( => {
// your code here
});
}, 2000);
});
});
这只是一个简单的 4 级代码,但我见过更多级别的嵌套,这并不好玩。
🌐 This is just a simple 4-levels code, but I've seen much more levels of nesting and it's not fun.
我们如何解决这个问题?
🌐 How do we solve this?
回调的替代方案
🌐 Alternatives to callbacks
从 ES6 开始,JavaScript 引入了几项帮助我们处理异步代码的功能,而无需使用回调:Promise(ES6)和 Async/Await(ES2017)。
🌐 Starting with ES6, JavaScript introduced several features that help us with asynchronous code that do not involve using callbacks: Promises (ES6) and Async/Await (ES2017).