发布于 2026-01-06 1 阅读
0

JavaScript异步编程和回调

JavaScript异步编程和回调

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()
Enter fullscreen mode Exit fullscreen mode

但 JavaScript 诞生于浏览器内部,它最初的主要任务是响应用户操作,例如点击onClickonMouseOver删除、onChange点击onSubmit等等。它如何能用同步编程模型做到这一点呢?

答案就在它所处的环境中。浏览器提供了一套API来实现这种功能。

最近,Node.js 引入了非阻塞 I/O 环境,将这一概念扩展到文件访问、网络调用等等。

回调函数

你无法预知用户何时会点击按钮,所以你需要为点击事件定义一个事件处理程序。这个事件处理程序接受一个函数作为参数,该函数会在事件触发时被调用:

document.getElementById('button').addEventListener('click', () => {
  //item clicked
})

Enter fullscreen mode Exit fullscreen mode

这就是所谓的回调函数

回调函数是一个简单的函数,它作为值传递给另一个函数,并且只有在事件发生时才会执行。之所以能够做到这一点,是因为 JavaScript 拥有“一等函数”,这些函数可以赋值给变量并传递给其他函数(称为高阶函数)。

通常的做法是将所有客户端代码封装在对象load的事件监听器中window,这样只有在页面准备就绪时才会运行回调函数:

window.addEventListener('load', () => {
  //window loaded
  //do what you want
})

Enter fullscreen mode Exit fullscreen mode

回调函数的应用非常广泛,不仅仅局限于 DOM 事件。

一个常见的例子是使用定时器:

setTimeout(() => {
  // runs after 2 seconds
}, 2000)

Enter fullscreen mode Exit fullscreen mode

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()

Enter fullscreen mode Exit fullscreen mode

回调函数中的错误处理

如何使用回调函数处理错误?一种非常常见的策略是采用 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)
})

Enter fullscreen mode Exit fullscreen mode

回调函数的问题

回调函数非常适合简单的情况!

然而,每个回调都会增加一层嵌套,当回调很多时,代码很快就会变得非常复杂:

window.addEventListener('load', () => {
  document.getElementById('button').addEventListener('click', () => {
    setTimeout(() => {
      items.forEach((item) => {
        //your code here
      })
    }, 2000)
  })
})

Enter fullscreen mode Exit fullscreen mode

这只是一个简单的 4 层嵌套代码,但我见过更多层嵌套的代码,那可不好玩。

我们该如何解决这个问题?

回调函数的替代方案

从 ES6 开始,JavaScript 引入了一些特性,可以帮助我们编写不涉及回调的异步代码:

文章来源:https://dev.to/flaviocopes/javascript-asynchronous-programming-and-callbacks-32jh