JavaScript异步编程和回调
编程语言中的异步性
计算机从设计上来说就是异步的。
异步意味着某些事情可以独立于主程序流程发生。
在目前的消费级电脑中,每个程序都只运行特定的时间段,然后停止执行,让另一个程序继续执行。这个过程循环往复,速度极快,几乎无法察觉。因此,我们误以为电脑可以同时运行多个程序,但这其实是一种错觉(多处理器电脑除外)。
程序内部使用中断,这是一种向处理器发出的信号,用于引起系统的注意。
我不会深入探讨其中的内部机制,但请记住,程序异步运行是正常的,它们会在需要处理之前暂停执行,而计算机在此期间可以执行其他任务。当程序等待网络响应时,它不能暂停处理器,直到请求完成。
通常,编程语言都是同步的,有些语言提供了在语言本身或通过库来管理异步操作的方法。C、Java、C#、PHP、Go、Ruby、Swift 和 Python 默认都是同步的。有些语言通过使用线程来处理异步操作,即创建一个新进程。
JavaScript
JavaScript 默认是同步的,并且是单线程的。这意味着代码不能创建新线程并并行运行。
代码行按顺序逐行执行,例如:
const a = 1
const b = 2
const c = a * b
console.log(c)
doSomething()
但 JavaScript 诞生于浏览器内部,它最初的主要任务是响应用户操作,例如点击onClick、onMouseOver删除、onChange点击onSubmit等等。它如何能用同步编程模型做到这一点呢?
答案就在它所处的环境中。浏览器提供了一套API来实现这种功能。
最近,Node.js 引入了非阻塞 I/O 环境,将这一概念扩展到文件访问、网络调用等等。
回调函数
你无法预知用户何时会点击按钮,所以你需要为点击事件定义一个事件处理程序。这个事件处理程序接受一个函数作为参数,该函数会在事件触发时被调用:
document.getElementById('button').addEventListener('click', () => {
//item clicked
})
这就是所谓的回调函数。
回调函数是一个简单的函数,它作为值传递给另一个函数,并且只有在事件发生时才会执行。之所以能够做到这一点,是因为 JavaScript 拥有“一等函数”,这些函数可以赋值给变量并传递给其他函数(称为高阶函数)。
通常的做法是将所有客户端代码封装在对象load的事件监听器中window,这样只有在页面准备就绪时才会运行回调函数:
window.addEventListener('load', () => {
//window loaded
//do what you want
})
回调函数的应用非常广泛,不仅仅局限于 DOM 事件。
一个常见的例子是使用定时器:
setTimeout(() => {
// runs after 2 seconds
}, 2000)
XHR 请求也接受回调函数,在本例中,可以通过将一个函数分配给一个属性来实现,该函数将在特定事件发生时调用(在本例中,请求状态发生变化):
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()
回调函数中的错误处理
如何使用回调函数处理错误?一种非常常见的策略是采用 Node.js 的做法:任何回调函数的第一个参数都是错误对象:错误优先回调。
如果没有错误,则对象为空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) => {
//your code here
})
}, 2000)
})
})
这只是一个简单的 4 层嵌套代码,但我见过更多层嵌套的代码,那可不好玩。
我们该如何解决这个问题?
回调函数的替代方案
从 ES6 开始,JavaScript 引入了一些特性,可以帮助我们编写不涉及回调的异步代码:
文章来源:https://dev.to/flaviocopes/javascript-asynchronous-programming-and-callbacks-32jh
