惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园_首页
T
Threat Research - Cisco Blogs
GbyAI
GbyAI
Y
Y Combinator Blog
美团技术团队
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - 【当耐特】
S
SegmentFault 最新的问题
IT之家
IT之家
Recent Announcements
Recent Announcements
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
阮一峰的网络日志
阮一峰的网络日志
T
The Blog of Author Tim Ferriss
Martin Fowler
Martin Fowler
Microsoft Azure Blog
Microsoft Azure Blog
V
Visual Studio Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
U
Unit 42
WordPress大学
WordPress大学
博客园 - Franky
L
LangChain Blog
人人都是产品经理
人人都是产品经理
小众软件
小众软件
博客园 - 叶小钗
罗磊的独立博客
酷 壳 – CoolShell
酷 壳 – CoolShell
大猫的无限游戏
大猫的无限游戏
云风的 BLOG
云风的 BLOG
Vercel News
Vercel News
雷峰网
雷峰网
腾讯CDC
Google DeepMind News
Google DeepMind News
博客园 - 三生石上(FineUI控件)
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Help Net Security
Help Net Security
C
Check Point Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
N
News and Events Feed by Topic
V2EX - 技术
V2EX - 技术
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Schneier on Security
Schneier on Security
博客园 - 聂微东
A
Arctic Wolf
H
Heimdal Security Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Recent Commits to openclaw:main
Recent Commits to openclaw:main
T
The Exploit Database - CXSecurity.com
C
Cyber Attacks, Cyber Crime and Cyber Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Google DeepMind News
Google DeepMind News

BAIYUN'S BLOG

AI Coding 从小工到专家 - BAI YUN 电信 IPTV 内网融合教程:在局域网任意设备观看 IPTV - BAI YUN 美国住宅 IP 提供商体验对比 - BAI YUN Node.js 程序以 Keyless 模式从 1Password 读取密钥 - BAI YUN 高效优雅的观影指南 - BAI YUN 基于 Surge 的 DNS 优化指南 - BAI YUN RIME 鼠须管输入法简明使用教程 - BAI YUN 自动给不同 Git 项目设置不同的用户名邮箱 - BAI YUN Web3 新手如何选择加密货币钱包 - BAI YUN Web3 到底是什么? - BAI YUN 谨慎开启 Safari 的阻止跨站跟踪功能 - BAI YUN iOS Surge 开启后无法使用部分国产 APP 或网络出错的解决办法 - BAI YUN 屏蔽 Telegram 垃圾私信的有效办法 - BAI YUN 每个人都应该读一遍《1984》和《动物庄园》 - BAI YUN iOS 给常见机构号码增加联系人头像 - BAI YUN 在国内如何购买比特币和投资加密货币 - BAI YUN WEB 性能优化:如何让你的网站页面秒开(方法论) - BAI YUN 加密货币投资这件事 - BAI YUN 国内手机号注册推特教程 - BAI YUN 国内注册币安账号并通过 KYC 教程 - BAI YUN 使用一套助记词创建多个以太坊钱包账户 - BAI YUN Apple Watch 的几个用途 - BAI YUN 钱包安全指南:如何保护你的 NFT 和加密资产不被盗 - BAI YUN 我在 macOS 上高频使用的应用 - BAI YUN Web3 时代:如何使用 MetaMask 钱包实现一键登录 - BAI YUN 2022 年了 Safari 无法渲染渐变文字的 BUG 依旧存在 - BAI YUN 境外手机卡:澳门电信大湾区预付卡使用体验 - BAI YUN 解决 iCloud Drive 上传文件卡住的问题 - BAI YUN Electron 应用如何获取系统代理配置 - BAI YUN 解决 iOS 上 Google 账号登录会卡在加载状态的问题 - BAI YUN 记录 Github Copilot 惊讶到我的时刻 - BAI YUN 解决 Xbox 在国内下载速度慢 - BAI YUN 开始使用 IndexNow - BAI YUN Surge iOS 和 AmpliFi 的兼容性问题 - BAI YUN 自建图床指南 - BAI YUN 使用 TypeScript 提升可维护性 - BAI YUN 在 JavaScript 应用中正确的实现时区切换 - BAI YUN 从 Loading 动画出发改进 React 应用的用户体验 - BAI YUN Node.js Tips - BAI YUN 使用 Dart Sass 代替 Node Sass - BAI YUN WSL 踩的坑和解决方法 - BAI YUN NPM & Yarn Tips - BAI YUN Git Tips - BAI YUN macOS 命令行 Tips - BAI YUN JavaScript 防劫持 - BAI YUN Dota2 - BAI YUN macOS 好用的系统维护工具 - BAI YUN 在 Linux 服务器上使用 Puppeteer 生成 PDF - BAI YUN The Golden Circle 黄金圆环法则 - BAI YUN CSS 写法进化史 - BAI YUN 一个 Web 开发者的 Windows10 开发环境设置心得 - BAI YUN Chrome DevTools Tips - BAI YUN React 设计模式和思想 - BAI YUN Linux & Unix 命令行环境的一些 Tips - BAI YUN 从传统 MVC 到同构:Web 发展历程 - BAI YUN Webpack 动态导入 - BAI YUN 关于我的博客 - BAI YUN
JavaScript 中的异步 - BAI YUN
baiyun · 2017-03-18 · via BAIYUN'S BLOG

什么是异步?

JavaScript 引擎是单线程的,这就意味着同一时间引擎自身只能做一件事,如果有很多事情要做,就必须一件一件来,在 JavaScript 中如果前面的某个任务需要耗费很长时间,后面的任务就被阻塞了,同时也无法响应用户的操作,例如 click 事件,看起来就像浏览器卡住了。

如果我们在浏览器中要通过 API 向服务器请求数据,就需要使用 XMLHttpRequest 对象发送一个 Ajax 请求,同时还会监听 HTTP 响应事件并指定一个回调函数来拿到这个数据,如果这个请求是同步的,那么在收到 HTTP 响应之前 JS 引擎就会一直处于阻塞状态,无法执行后面的代码也无法响应用户的交互操作。

如果是异步的就不会通过阻塞 JS 引擎的方式来等待 HTTP 响应,JS 引擎会告诉宿主环境(浏览器或者 Node)在收到 HTTP 响应之后将回调函数插入事件循环队列的末尾,然后自己会继续执行后面的代码,在未来的某个时间点,宿主环境收到这个 HTTP 响应之后就会将回调函数插入事件循环队列的末尾,在事件循环队列里的回调函数最终都会被 JS 引擎按顺序一一执行。

事件循环 (Event Loop)

在 You Don't Know JS 中有一段代码可以很形象的表现出事件循环的基本模型

// `eventLoop` is an array that acts as a queue (first-in, first-out)
var eventLoop = []
var event

// keep going "forever"
while (true) {
  // perform a "tick"
  if (eventLoop.length > 0) {
    // get the next event in the queue
    event = eventLoop.shift()

    // now, execute the next event
    try {
      event()
    } catch (err) {
      reportError(err)
    }
  }
}

可以这么理解,JS 引擎在执行完同步代码之后(或者说 call stack 变空后)就会执行上面这个 while 死循环,程序初始化之后触发的所有操作都会完成后将对应的回调函数 push 到事件循环队列里,JS 引擎会一个接一个按顺序取出队列里的回调函数执行它。

有哪些操作是异步的?

通常有以下几种情况会触发异步操作:

  • setTimeout
  • setInterval
  • Promise
  • XMLHttpRequest
  • 事件处理器

最简单的一个例子:

setTimeout(() => console.log(2), 0)
console.log(1)

第一行表示立即将 setTimeout 的第一个参数添加到任务队列末尾,接着执行第二行, 最后才会执行任务队列中的任务,最终会打印出 1 2

需要注意的是,setTimeout 和 setInterval 在执行时间上是不可靠的,setTimeout 表示在指定的延迟后将第一个参数添加到任务队列末尾,setInterval 表示每隔指定的时间就将第一个参数添加到任务队列末尾。

setTimeout(() => console.log(2), 0)
task() // 假设这个函数会耗时 10 秒

上面的代码在开始执行时就会立即将 setTimeout 的第一个参数添加的任务队列末尾,但是在 10 秒后才会打印出 0

setInterval(() => console.log(new Date()), 2000) // 本意表示每隔两秒打印一次当前时间
task() // 假设这个函数会耗时 10 秒

上面的代码会在 10 秒后连续 5 次打印出当前时间, 原因就在于每隔指定的时间 setInterval 就会不分青红皂白无脑将第一个参数添加到任务队列末尾,等到 javascript 主线程空闲了开始取出队列中的任务执行时也是无脑的,只要队列中还有任务它就会一个接一个的取出来执行,所以传给 setInterval 的函数也是无法保证执行时间的。

如果你需要使用 setTimeout 实现一个动画,可以使用 requestAnimationFrame 代替,如果要兼容不支持 requestAnimationFrame 的浏览器,可以使用浏览器特性检测区别对待。

在浏览器环境下异步任务基本分为以下两种类型:

  • macrotasks: setTimeout, setInterval
  • microtasks: Promises, MutationObserver

Promise 使用的 microtasks

考虑如下代码

setTimeout(() => console.log(0), 0) // 注意这里
const promise = new Promise(function(resolve, reject) {
  console.log(1)
  resolve()
})
promise.then(() => console.log(2))
console.log(3)

Promise 的执行器会在 Promise 创建时立即执行,所以会首先打印出 1,执行器的 resolvereject 函数都是异步操作,传给 promise.then 的回调函数会在执行器内部的 resolve 函数执行后执行,如果上面的代码去掉第一行,那么最终会依次输出 1 3 2

如果没有去掉第一行,就会输出 1 3 2 0

上面代码中 Promise 的执行器内部的 resolve 和 reject 函数虽然是异步操作,但是它们并不会添加到 macrotasks,而是添加到 microtasks,它会在每次 Event Loop 迭代结束之前全部执行完毕,所以等主线程空下来的时候会先执行完 microtasks 中的所有任务才会开始执行 macrotasks 中的任务。

  • 所有传递给 promise.then 或者 promise.catch 的函数都是异步的并且会被添加到 microtasks 中。
  • Node.js 中的异步和浏览器环境中的异步并不完全一致,要比浏览器复杂一些

常见异步任务的种类和优先级

微任务 MicroTask

例如 Promise 的 onRejected 和 onFulfilled 回调函数就是微任务,其他还有 window.queueMicrotask

requestAnimationFrame

传给 requestAnimationFrame 的回调函数浏览器会在下一次重绘之前调用

宏任务 MacroTask

常见的有 window.setTimeout 以及各种 DOM 交互事件,例如点击和滚动事件。

优先级

微任务 > requestAnimationFrame > 宏任务

举例

执行以下代码

setTimeout(() => {
  console.log(3)
}, 0)
Promise.resolve().then(() => {
  console.log(2)
})
console.log(1)

上面的代码最终输出:

1
2
3

参考

本文原载于:baiyun.me

原文链接:https://baiyun.me/javascript-asynchronous