JavaScript 异步编程之回调函数
原文链接:flaviocopes.com/javascript-…
JavaScript 是同步并且是单线程的。这意味着 JavaScript 代码不能创建新的线程并且并行运行。让我们一起来看看什么是异步代码吧。
- 编程语言中的异步
- JavaScript
- 回调函数
- 处理回调函数中的错误
- 回调的问题
- 回调的替代方案
编程语言中的回调
计算机是异步的。
异步意味着在主流程之外,其他事件可以独立进行。在现代计算机中,每个程序都是在特定的时间槽运行的,要运行其他程序时,计算机会把当前运行中的程序停止掉。如此循环的切换运行不同的程序,看起来像是计算机同时运行着多个程序(除了多处理器计算机)。
异步在程序中非常常见,计算机会在异步事件需要执行时,才执行代码,其他时间计算机会做其他事情。例如,当一个程序正在等待网络请求的响应时,它不会阻断处理器的执行,直到请求完成。
编程语言通常是同步的,有些语言内部或者库里提供了异步处理方法,例如 C, Java, C#, PHP, Go, Ruby, Swift, Python。 一些语言通过产出新线程去处理异步。
JavaScript
JavaScript 天生是同步并且是单线程的。这意味着它不能创建新线程或者并发执行。
但是 JavaScript 是在浏览器中诞生的,它的主要工作是处理用户的动作,例如 onClick
, onMouseOver
, onChange
, onSubmit
等动作。它如何在同步编程模型中做到这一点?
答案是浏览器环境。浏览器提供了一系列的接口处理实现这些功能。
最近,node.js引入了一个非阻塞I/O环境,把异步扩展到文件访问、网络调用等。
回调
你不可能知道用户什么时候会点击按钮,所以你只能定义一个事件处理者(event handler)处理点击事件。这个事件处理者接收一个函数,当点击时间发生的时候,这个函数就会被触发:
document.getElementById('button').addEventListener('click', () => {
console.log('点击事件');
})
复制代码
这就是所谓的回调。
回调是一个简单的函数,它作为参数传递给另一个函数,并且在事件发生的时候会执行。在 JavaScript中,函数可以赋值给一个变量,作为其他函数的参数。
通常我们会把客户端代码包在 window 的load
事件监听里,当页面加载完毕后,回调函数才会执行:
window.addEventListener('load', () => {
// window load
// do what you want
})
复制代码
除了 DOM 事件中,回调被用在各种地方,最常见的例子是计时器:
setTimeout(() => {
// 两秒后执行
}, 2000)
复制代码
XHR 请求也接受回调函数,在这个例子中,回调函数传入了 xhr 的 onreadystatechange 方法中,当请求状态发生变化时,触发回调函数的执行:
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
xhr.status === 200 ? console.log(xhr.responseText) : console.error('error')
}
}
xhr.open('GET', 'https://yoursite.com')
xhr.send()
复制代码
回调中的错误处理
你如何处理回调中的错误呢?一个常见的策略是 Nodejs 采用的办法:任何回调函数中的第一个参数是错误对象:error-first callbacks
如果没有出错,这个对象将会是 null。如果有错,该对象会包含一些错误描述或者其他信息。
fs.readFile('/file.json', (err, data) => {
if (err !== null) {
//handle error
console.log(err)
return
}
//no errors, process data
console.log(data)
})
复制代码
回调的问题
回调对于简单问题来说非常好。但是每多一次回调就回增加一层嵌套,当你有非常多的回调时,代码就回变得非常复杂:
window.addEventListener('load', () => {
document.getElementById('button').addEventListener('click', () => {
setTimeout(() => {
items.forEach(item => {
// 你的代码
})
}, 2000)
})
})
复制代码
这仅仅是简单的四级代码,但我已经看到了很多级嵌套,不是很友好。
怎么解决这个问题?
回调的替代方案
从 ES6 开始, JavaScript 引入了一些特性帮助我们处理异步代码: