首页 文章 精选 留言 我的

精选列表

搜索[node.js],共4383篇文章
优秀的个人博客,低调大师

Node.js 中 fs.createReadStream 的 end 事件为何不触发?一个流式读取大文件的真实坑

上周排查一个日志归档脚本时遇到一个很典型的问题:用 fs.createReadStream 读取几百 MB 的日志文件,逐行解析后压缩上传,结果 end 事件根本不会触发,进程静默卡住,没有任何报错。这个场景在数据处理、文件上传、ETL 脚本里很常见。原因通常不是流本身有问题,而是读取过程中 data 事件的回调里触发了背压或者同步异常,导致流不再继续泵数据,也可能是在 close 之前就移除了监听器。本文围绕一个真实的流式读取代码,拆解 end、close、error 之间的触发顺序,解释背压时 readable 和 data 两种消费方式的差异,并给出可靠的处理方式,让大文件读取不再莫名卡死。

优秀的个人博客,低调大师

在Node.js里处理multipart/form-data时,为什么用busboy比用body-parser更稳妥

做文件上传接口的时候,很多人第一反应是挂上 `express.json()` 和 `express.urlencoded()`,然后发现 `req.body` 里空空如也,或者只拿到一个奇怪的字段。这个问题通常出现在前端用 `FormData` 提交、Content-Type 是 `multipart/form-data; boundary=...` 的场景。`express.json()` 只认 `application/json`,`urlencoded` 只处理 `application/x-www-form-urlencoded`,它们都不会去解析分段边界和二进制流。更麻烦的是,如果先把整个请求体缓冲成字符串再处理,遇到几 MB 的图片或混入二进制数据时,内存占用和编码转换都可能出问题。这篇文章从实际代码出发,讲清楚 multipart 的边界结构,演示如何用 busboy 以流的方式拿到普通字段和文件,并处理临时文件、大小限制、中文文件名和错误中断这些容易踩的细节。 先看一眼真实请求体的结构。浏览器用 FormData 提交时,请求头里会有类似这样的内容: ```http Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW ``` body 里每一段都以 `--boundary` 开始,字段名放在 `Content-Disposition: form-data; name="..."` 里,文件还会带 `filename` 和 `Content-Type`。最后以 `--boundary--` 结束。如果用字符串拼接的方式去切分,很容易在二进制内容里误判边界,尤其是文件内容恰好包含类似 `--` 加 boundary 的字节序列时。 busboy 的做法是监听流上的事件,把每个 part 当成独立的可读流处理。下面是一个不依赖中间件的原生 http 例子: ```js const http = require('http') const Busboy = require('busboy') const fs = require('fs') const path = require('path') const os = require('os') const server = http.createServer((req, res) => { if (req.method !== 'POST' || !req.headers['content-type']?.startsWith('multipart/form-data')) { res.writeHead(400) return res.end('expected multipart/form-data') } const fields = {} const busboy = Busboy({ headers: req.headers, limits: { fileSize: 5 * 1024 * 1024, files: 2, fields: 10 } }) busboy.on('field', (name, val) => { fields[name] = val }) busboy.on('file', (name, stream, info) => { const { filename, encoding, mimeType } = info // 浏览器提交空文件输入时 filename 可能是空字符串 if (!filename) { stream.resume() return } const safeName = path.basename(filename).replace(/[^\w.\u4e00-\u9fa5-]/g, '_') const tmpPath = path.join(os.tmpdir(), `${Date.now()}-${safeName}`) const ws = fs.createWriteStream(tmpPath) stream.on('limit', () => { ws.destroy() fs.unlink(tmpPath, () => {}) res.writeHead(413) res.end('file too large') }) stream.pipe(ws) ws.on('finish', () => { fields[name] = { tmpPath, filename: safeName, mimeType, encoding } }) }) busboy.on('error', (err) => { res.writeHead(400) res.end('bad multipart: ' + err.message) }) busboy.on('finish', () => { // 注意:finish 只代表解析结束,文件写入可能还在进行 res.writeHead(200, { 'Content-Type': 'application/json' }) res.end(JSON.stringify(fields)) }) req.pipe(busboy) }) server.listen(3000) ``` 这段代码里有个容易被忽略的点:`busboy.on('finish')` 触发时,文件流可能还没写完。如果直接在 finish 里返回结果,拿到的临时文件路径可能还不存在。稳妥的做法是用 `Promise` 或者维护一个写入计数,等所有 `ws.on('finish')` 都完成后再响应。我在实际项目里更倾向于把文件处理包成 Promise: ```js const busboy = Busboy({ headers: req.headers, limits: { fileSize: 5 * 1024 * 1024 } }) const fields = {} const fileTasks = [] busboy.on('file', (name, stream, info) => { if (!info.filename) { stream.resume() return } const task = new Promise((resolve, reject) => { const tmpPath = path.join(os.tmpdir(), `${Date.now()}-${path.basename(info.filename)}`) const ws = fs.createWriteStream(tmpPath) stream.on('limit', () => reject(Object.assign(new Error('file too large'), { status: 413 }))) stream.on('error', reject) ws.on('error', reject) ws.on('finish', () => resolve({ name, tmpPath, info })) stream.pipe(ws) }) fileTasks.push(task) }) busboy.on('field', (name, val) => { fields[name] = val }) busboy.on('finish', async () => { try { const files = await Promise.all(fileTasks) // 这里再落库或转存对象存储 console.log(fields, files) } catch (err) { // 清理已经写出的临时文件 } }) req.pipe(busboy) ``` 如果是在 Express 里,不要试图用 `body-parser` 去接 multipart,它本来就不管这个。可以选择 `multer`,它内部也是基于 busboy 封装的。但有些老项目里 multer 版本和 Express 4/5 的兼容问题会带来额外排查成本,直接用 busboy 反而更透明。比如需要边解析边把文件流转发到对象存储,而不是先落盘,busboy 的事件模型就很合适: ```js busboy.on('file', (name, stream, info) => { const upload = storage.createWriteStream({ key: info.filename }) stream.pipe(upload) }) ``` 再提几个实际会遇到的坑。中文文件名在部分客户端里会以 `filename*=` 的 RFC 5987 形式出现,busboy 新版本会做解码,但老版本可能拿到 `UTF-8''%E4%B8%AD%E6%96%87` 这样的原始值,需要自己 `decodeURIComponent`。另外,`limits` 里的 `fileSize` 触发后流会报 `limit` 事件,但请求本身不会自动断开,如果客户端还在继续发数据,最好主动 `req.unpipe(busboy)` 并销毁连接,否则会白白消耗带宽。还有一个常见问题是前面挂了 Nginx,默认 `client_max_body_size` 是 1MB,大文件请求会被 Nginx 直接返回 413,这时候 Node 侧根本收不到请求,排查时先看反向代理配置。 如果不需要保留文件,只是解析字段,可以用 `stream.resume()` 丢弃文件内容,避免写盘。但要注意 `resume()` 之后仍然要等流结束,否则 busboy 可能卡在解析状态。对于需要做类型校验的场景,在 `file` 事件里根据 `info.mimeType` 判断,不要只信任扩展名。最后,任何临时文件都要在请求结束或异常时清理,尤其是上面那种先落盘再处理的写法,`finish` 和 `error` 两条路径都得覆盖到。 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1401.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1402.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1403.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1404.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1405.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1406.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1407.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1408.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1409.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1410.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1411.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1412.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1413.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1414.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1415.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1416.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1417.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1418.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1419.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1420.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1421.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1422.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1423.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1424.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1425.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1426.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1427.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1428.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1429.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1430.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1431.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1432.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1433.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1434.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1435.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1436.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1437.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1438.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1439.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1440.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1441.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1442.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1443.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1444.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1445.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1446.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1447.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1448.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1449.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1450.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1451.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1452.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1453.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1454.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1455.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1456.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1457.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1458.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1459.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1460.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1461.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1462.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1463.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1464.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1465.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1466.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1467.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1468.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1469.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1470.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1471.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1472.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1473.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1474.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1475.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1476.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1477.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1478.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1479.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1480.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1481.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1482.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1483.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1484.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1485.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1486.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1487.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1488.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1489.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1490.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1491.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1492.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1493.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1494.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1495.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1496.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1497.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1498.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1499.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1500.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1501.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1502.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1503.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1504.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1505.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1506.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1507.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1508.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1509.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1510.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1511.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1512.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1513.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1514.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1515.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1516.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1517.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1518.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1519.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1520.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1521.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1522.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1523.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1524.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1525.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1526.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1527.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1528.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1529.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1530.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1531.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1532.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1533.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1534.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1535.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1536.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1537.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1538.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1539.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1540.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1541.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1542.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1543.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1544.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1545.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1546.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1547.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1548.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1549.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1550.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1551.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1552.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1553.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1554.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1555.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1556.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1557.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1558.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1559.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1560.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1561.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1562.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1563.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1564.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1565.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1566.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1567.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1568.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1569.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1570.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1571.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1572.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1573.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1574.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1575.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1576.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1577.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1578.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1579.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1580.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1581.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1582.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1583.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1584.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1585.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1586.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1587.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1588.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1589.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1590.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1591.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1592.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1593.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1594.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1595.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1596.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1597.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1598.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1599.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/1600.html

优秀的个人博客,低调大师

Node.js 中使用 worker_threads 处理 CPU 密集型任务时,为什么你的接口还是卡住了?

有一个后台管理项目需要导出十万条订单数据,每一条都要做金额格式化、时间转换和状态映射。最开始直接在请求处理函数里写了个 for 循环,本地测试几百条数据没问题,上线后只要有人点导出,整个 Node 服务就像被冻住一样,所有接口的响应时间都飙到好几秒。后来把导出逻辑挪到 worker_threads 里,接口是不卡了,但导出任务本身慢得离谱,还偶尔报 `ERR_WORKER_OUT_OF_MEMORY`。排查后发现,问题不在于用没用 worker,而在于数据在主线程和 worker 之间来回传递的方式,以及 worker 的创建和销毁频率。这篇文章从实际场景出发,梳理 worker_threads 的通信机制、结构化克隆的开销、worker 池的合理用法,以及 SharedArrayBuffer 和 transferList 在什么情况下才真正划算。 ## 为什么 move 到 worker 里还是慢 很多人对 worker_threads 的预期是“把任务丢过去就完事了”。但主线程和 worker 默认不共享内存,两边通过 `postMessage` 通信时,传过去的数据会被结构化克隆。十万条订单对象,每条有十几个字段,克隆一次就是几十 MB 的内存分配和序列化开销。导出任务里通常还要把结果再传回主线程,于是同样的开销又来一遍。 看一段典型的错误写法: ```js // main.js const { Worker } = require('worker_threads'); function exportOrders(orders) { return new Promise((resolve, reject) => { const worker = new Worker('./export-worker.js', { workerData: { orders } // 十万条数据直接塞进去 }); worker.on('message', resolve); worker.on('error', reject); worker.on('exit', (code) => { if (code !== 0) reject(new Error(`Worker stopped with exit code ${code}`)); }); }); } // 每个请求都 new 一个 Worker app.get('/export', async (req, res) => { const orders = await db.getOrders(); const csv = await exportOrders(orders); res.send(csv); }); ``` 这个写法有三个问题:每次请求都创建一个 worker,Node 创建线程的成本虽然比进程低,但依然不便宜;`workerData` 里的十万条数据被完整克隆;worker 执行完就退出,下次请求重新来一遍。 ## worker 不是越新鲜越好 Worker 应该复用。一个 worker 启动后可以长期存活,通过消息接收任务。更合理的做法是维护一个 worker 池,任务排队,worker 从队列里取活干。Node 官方没有内置 worker 池,但可以用 `workerpool` 这样的库,或者自己写一个简单的调度器。 ```js // pool.js const { Worker } = require('worker_threads'); const os = require('os'); class WorkerPool { constructor(workerPath, size = os.cpus().length) { this.workerPath = workerPath; this.size = size; this.workers = []; this.queue = []; this.init(); } init() { for (let i = 0; i { const task = worker.currentTask; worker.currentTask = null; if (task) task.resolve(result); this.next(worker); }); worker.on('error', (err) => { if (worker.currentTask) worker.currentTask.reject(err); worker.currentTask = null; this.next(worker); }); this.workers.push(worker); } } run(data) { return new Promise((resolve, reject) => { const task = { data, resolve, reject }; const idle = this.workers.find(w => !w.currentTask); if (idle) { this.assign(idle, task); } else { this.queue.push(task); } }); } assign(worker, task) { worker.currentTask = task; worker.postMessage(task.data); } next(worker) { if (this.queue.length) { this.assign(worker, this.queue.shift()); } } } ``` 池子大小默认取 CPU 核心数,因为 CPU 密集型任务再怎么开线程,物理核心就那么多,开太多反而增加上下文切换。如果是 I/O 密集的 worker 任务,可以适当放大。 ## 大数据量下,减少克隆才是关键 即便复用了 worker,`postMessage` 的克隆开销依然存在。如果数据量真的很大,有几个方向可以考虑。 把数据拆成小块分批发送。不要一次 post 十万条,而是每次五百条,worker 处理完一批返回一批。这样单次克隆的内存峰值可控,主线程也不会因为一次巨大的序列化操作而卡顿。 ```js // 主线程分批发送 async function exportInBatches(orders, batchSize = 500) { const results = []; for (let i = 0; i { console.error('Worker error:', err); // 这里必须处理,否则主线程可能崩溃 }); ``` 在用 worker_threads 之前,先确认瓶颈真的是 CPU。有时候接口慢是因为数据库查询没加索引,或者同步读取了大文件,这种情况下 worker 帮不上忙,只会让架构更复杂。 推荐查看天气:http://www.362tq.com/nanxiong/40tian.html 推荐查看天气:http://www.362tq.com/shenzhen/ 推荐查看天气:http://www.362tq.com/shenzhen/zuotian.html 推荐查看天气:http://www.362tq.com/shenzhen/7tian.html 推荐查看天气:http://www.362tq.com/shenzhen/10tian.html 推荐查看天气:http://www.362tq.com/shenzhen/15tian.html 推荐查看天气:http://www.362tq.com/shenzhen/40tian.html 推荐查看天气:http://www.362tq.com/luohu/ 推荐查看天气:http://www.362tq.com/luohu/zuotian.html 推荐查看天气:http://www.362tq.com/luohu/7tian.html 推荐查看天气:http://www.362tq.com/luohu/10tian.html 推荐查看天气:http://www.362tq.com/luohu/15tian.html 推荐查看天气:http://www.362tq.com/luohu/40tian.html 推荐查看天气:http://www.362tq.com/futian/ 推荐查看天气:http://www.362tq.com/futian/zuotian.html 推荐查看天气:http://www.362tq.com/futian/7tian.html 推荐查看天气:http://www.362tq.com/futian/10tian.html 推荐查看天气:http://www.362tq.com/futian/15tian.html 推荐查看天气:http://www.362tq.com/futian/40tian.html 推荐查看天气:http://www.362tq.com/nanshan/ 推荐查看天气:http://www.362tq.com/nanshan/zuotian.html 推荐查看天气:http://www.362tq.com/nanshan/7tian.html 推荐查看天气:http://www.362tq.com/nanshan/10tian.html 推荐查看天气:http://www.362tq.com/nanshan/15tian.html 推荐查看天气:http://www.362tq.com/nanshan/40tian.html 推荐查看天气:http://www.362tq.com/baoan/ 推荐查看天气:http://www.362tq.com/baoan/zuotian.html 推荐查看天气:http://www.362tq.com/baoan/7tian.html 推荐查看天气:http://www.362tq.com/baoan/10tian.html 推荐查看天气:http://www.362tq.com/baoan/15tian.html 推荐查看天气:http://www.362tq.com/baoan/40tian.html 推荐查看天气:http://www.362tq.com/longgang/ 推荐查看天气:http://www.362tq.com/longgang/zuotian.html 推荐查看天气:http://www.362tq.com/longgang/7tian.html 推荐查看天气:http://www.362tq.com/longgang/10tian.html 推荐查看天气:http://www.362tq.com/longgang/15tian.html 推荐查看天气:http://www.362tq.com/longgang/40tian.html 推荐查看天气:http://www.362tq.com/yantian/ 推荐查看天气:http://www.362tq.com/yantian/zuotian.html 推荐查看天气:http://www.362tq.com/yantian/7tian.html 推荐查看天气:http://www.362tq.com/yantian/10tian.html 推荐查看天气:http://www.362tq.com/yantian/15tian.html 推荐查看天气:http://www.362tq.com/yantian/40tian.html 推荐查看天气:http://www.362tq.com/zhuhai/ 推荐查看天气:http://www.362tq.com/zhuhai/zuotian.html 推荐查看天气:http://www.362tq.com/zhuhai/7tian.html 推荐查看天气:http://www.362tq.com/zhuhai/10tian.html 推荐查看天气:http://www.362tq.com/zhuhai/15tian.html 推荐查看天气:http://www.362tq.com/zhuhai/40tian.html 推荐查看天气:http://www.362tq.com/doumen/ 推荐查看天气:http://www.362tq.com/doumen/zuotian.html 推荐查看天气:http://www.362tq.com/doumen/7tian.html 推荐查看天气:http://www.362tq.com/doumen/10tian.html 推荐查看天气:http://www.362tq.com/doumen/15tian.html 推荐查看天气:http://www.362tq.com/doumen/40tian.html 推荐查看天气:http://www.362tq.com/jinwan/ 推荐查看天气:http://www.362tq.com/jinwan/zuotian.html 推荐查看天气:http://www.362tq.com/jinwan/7tian.html 推荐查看天气:http://www.362tq.com/jinwan/10tian.html 推荐查看天气:http://www.362tq.com/jinwan/15tian.html 推荐查看天气:http://www.362tq.com/jinwan/40tian.html 推荐查看天气:http://www.362tq.com/shantou/ 推荐查看天气:http://www.362tq.com/shantou/zuotian.html 推荐查看天气:http://www.362tq.com/shantou/7tian.html 推荐查看天气:http://www.362tq.com/shantou/10tian.html 推荐查看天气:http://www.362tq.com/shantou/15tian.html 推荐查看天气:http://www.362tq.com/shantou/40tian.html 推荐查看天气:http://www.362tq.com/longhu/ 推荐查看天气:http://www.362tq.com/longhu/zuotian.html 推荐查看天气:http://www.362tq.com/longhu/7tian.html 推荐查看天气:http://www.362tq.com/longhu/10tian.html 推荐查看天气:http://www.362tq.com/longhu/15tian.html 推荐查看天气:http://www.362tq.com/longhu/40tian.html 推荐查看天气:http://www.362tq.com/haojiang/ 推荐查看天气:http://www.362tq.com/haojiang/zuotian.html 推荐查看天气:http://www.362tq.com/haojiang/7tian.html 推荐查看天气:http://www.362tq.com/haojiang/10tian.html 推荐查看天气:http://www.362tq.com/haojiang/15tian.html 推荐查看天气:http://www.362tq.com/haojiang/40tian.html 推荐查看天气:http://www.362tq.com/shantouchaoyang/ 推荐查看天气:http://www.362tq.com/shantouchaoyang/zuotian.html 推荐查看天气:http://www.362tq.com/shantouchaoyang/7tian.html 推荐查看天气:http://www.362tq.com/shantouchaoyang/10tian.html 推荐查看天气:http://www.362tq.com/shantouchaoyang/15tian.html 推荐查看天气:http://www.362tq.com/shantouchaoyang/40tian.html 推荐查看天气:http://www.362tq.com/chaonan/ 推荐查看天气:http://www.362tq.com/chaonan/zuotian.html 推荐查看天气:http://www.362tq.com/chaonan/7tian.html 推荐查看天气:http://www.362tq.com/chaonan/10tian.html 推荐查看天气:http://www.362tq.com/chaonan/15tian.html 推荐查看天气:http://www.362tq.com/chaonan/40tian.html 推荐查看天气:http://www.362tq.com/chenghai/ 推荐查看天气:http://www.362tq.com/chenghai/zuotian.html 推荐查看天气:http://www.362tq.com/chenghai/7tian.html 推荐查看天气:http://www.362tq.com/chenghai/10tian.html 推荐查看天气:http://www.362tq.com/chenghai/15tian.html 推荐查看天气:http://www.362tq.com/chenghai/40tian.html 推荐查看天气:http://www.362tq.com/nanao/ 推荐查看天气:http://www.362tq.com/nanao/zuotian.html 推荐查看天气:http://www.362tq.com/nanao/7tian.html 推荐查看天气:http://www.362tq.com/nanao/10tian.html 推荐查看天气:http://www.362tq.com/nanao/15tian.html 推荐查看天气:http://www.362tq.com/nanao/40tian.html 推荐查看天气:http://www.362tq.com/foshan/ 推荐查看天气:http://www.362tq.com/foshan/zuotian.html 推荐查看天气:http://www.362tq.com/foshan/7tian.html 推荐查看天气:http://www.362tq.com/foshan/10tian.html 推荐查看天气:http://www.362tq.com/foshan/15tian.html 推荐查看天气:http://www.362tq.com/foshan/40tian.html 推荐查看天气:http://www.362tq.com/chancheng/ 推荐查看天气:http://www.362tq.com/chancheng/zuotian.html 推荐查看天气:http://www.362tq.com/chancheng/7tian.html 推荐查看天气:http://www.362tq.com/chancheng/10tian.html 推荐查看天气:http://www.362tq.com/chancheng/15tian.html 推荐查看天气:http://www.362tq.com/chancheng/40tian.html 推荐查看天气:http://www.362tq.com/nanhai/ 推荐查看天气:http://www.362tq.com/nanhai/zuotian.html 推荐查看天气:http://www.362tq.com/nanhai/7tian.html 推荐查看天气:http://www.362tq.com/nanhai/10tian.html 推荐查看天气:http://www.362tq.com/nanhai/15tian.html 推荐查看天气:http://www.362tq.com/nanhai/40tian.html 推荐查看天气:http://www.362tq.com/shunde/ 推荐查看天气:http://www.362tq.com/shunde/zuotian.html 推荐查看天气:http://www.362tq.com/shunde/7tian.html 推荐查看天气:http://www.362tq.com/shunde/10tian.html 推荐查看天气:http://www.362tq.com/shunde/15tian.html 推荐查看天气:http://www.362tq.com/shunde/40tian.html 推荐查看天气:http://www.362tq.com/sanshui/ 推荐查看天气:http://www.362tq.com/sanshui/zuotian.html 推荐查看天气:http://www.362tq.com/sanshui/7tian.html 推荐查看天气:http://www.362tq.com/sanshui/10tian.html 推荐查看天气:http://www.362tq.com/sanshui/15tian.html 推荐查看天气:http://www.362tq.com/sanshui/40tian.html 推荐查看天气:http://www.362tq.com/gaoming/ 推荐查看天气:http://www.362tq.com/gaoming/zuotian.html 推荐查看天气:http://www.362tq.com/gaoming/7tian.html 推荐查看天气:http://www.362tq.com/gaoming/10tian.html 推荐查看天气:http://www.362tq.com/gaoming/15tian.html 推荐查看天气:http://www.362tq.com/gaoming/40tian.html 推荐查看天气:http://www.362tq.com/jiangmen/ 推荐查看天气:http://www.362tq.com/jiangmen/zuotian.html 推荐查看天气:http://www.362tq.com/jiangmen/7tian.html 推荐查看天气:http://www.362tq.com/jiangmen/10tian.html 推荐查看天气:http://www.362tq.com/jiangmen/15tian.html 推荐查看天气:http://www.362tq.com/jiangmen/40tian.html 推荐查看天气:http://www.362tq.com/pengjiang/ 推荐查看天气:http://www.362tq.com/pengjiang/zuotian.html 推荐查看天气:http://www.362tq.com/pengjiang/7tian.html 推荐查看天气:http://www.362tq.com/pengjiang/10tian.html 推荐查看天气:http://www.362tq.com/pengjiang/15tian.html 推荐查看天气:http://www.362tq.com/pengjiang/40tian.html 推荐查看天气:http://www.362tq.com/jianghai/ 推荐查看天气:http://www.362tq.com/jianghai/zuotian.html 推荐查看天气:http://www.362tq.com/jianghai/7tian.html 推荐查看天气:http://www.362tq.com/jianghai/10tian.html 推荐查看天气:http://www.362tq.com/jianghai/15tian.html 推荐查看天气:http://www.362tq.com/jianghai/40tian.html 推荐查看天气:http://www.362tq.com/xinhui/ 推荐查看天气:http://www.362tq.com/xinhui/zuotian.html 推荐查看天气:http://www.362tq.com/xinhui/7tian.html 推荐查看天气:http://www.362tq.com/xinhui/10tian.html 推荐查看天气:http://www.362tq.com/xinhui/15tian.html 推荐查看天气:http://www.362tq.com/xinhui/40tian.html 推荐查看天气:http://www.362tq.com/taishan/ 推荐查看天气:http://www.362tq.com/taishan/zuotian.html 推荐查看天气:http://www.362tq.com/taishan/7tian.html 推荐查看天气:http://www.362tq.com/taishan/10tian.html 推荐查看天气:http://www.362tq.com/taishan/15tian.html 推荐查看天气:http://www.362tq.com/taishan/40tian.html 推荐查看天气:http://www.362tq.com/kaiping/ 推荐查看天气:http://www.362tq.com/kaiping/zuotian.html 推荐查看天气:http://www.362tq.com/kaiping/7tian.html 推荐查看天气:http://www.362tq.com/kaiping/10tian.html 推荐查看天气:http://www.362tq.com/kaiping/15tian.html 推荐查看天气:http://www.362tq.com/kaiping/40tian.html 推荐查看天气:http://www.362tq.com/jiangmenheshan/ 推荐查看天气:http://www.362tq.com/jiangmenheshan/zuotian.html 推荐查看天气:http://www.362tq.com/jiangmenheshan/7tian.html 推荐查看天气:http://www.362tq.com/jiangmenheshan/10tian.html 推荐查看天气:http://www.362tq.com/jiangmenheshan/15tian.html 推荐查看天气:http://www.362tq.com/jiangmenheshan/40tian.html 推荐查看天气:http://www.362tq.com/enping/ 推荐查看天气:http://www.362tq.com/enping/zuotian.html 推荐查看天气:http://www.362tq.com/enping/7tian.html 推荐查看天气:http://www.362tq.com/enping/10tian.html 推荐查看天气:http://www.362tq.com/enping/15tian.html 推荐查看天气:http://www.362tq.com/enping/40tian.html 推荐查看天气:http://www.362tq.com/zhanjiang/ 推荐查看天气:http://www.362tq.com/zhanjiang/zuotian.html 推荐查看天气:http://www.362tq.com/zhanjiang/7tian.html 推荐查看天气:http://www.362tq.com/zhanjiang/10tian.html 推荐查看天气:http://www.362tq.com/zhanjiang/15tian.html 推荐查看天气:http://www.362tq.com/zhanjiang/40tian.html 推荐查看天气:http://www.362tq.com/chikan/ 推荐查看天气:http://www.362tq.com/chikan/zuotian.html 推荐查看天气:http://www.362tq.com/chikan/7tian.html 推荐查看天气:http://www.362tq.com/chikan/10tian.html 推荐查看天气:http://www.362tq.com/chikan/15tian.html 推荐查看天气:http://www.362tq.com/chikan/40tian.html 推荐查看天气:http://www.362tq.com/xiashan/

优秀的个人博客,低调大师

Node.js 中 process.nextTick 与 setImmediate 的执行顺序陷阱:一次真实的 I/O 回调调试记录

上周排查一个文件上传服务时遇到一个诡异现象:在 fs.readFile 的回调里调用 process.nextTick,预期它会在 setImmediate 之前执行,但实际日志打印顺序却相反。这段代码在本地开发环境跑得好好的,部署到 Docker 容器后行为就变了。问题根源不是 Node 版本差异,而是对事件循环中 nextTick 队列和 check 阶段的执行时机理解有偏差。很多人在写异步流程控制时习惯用 nextTick 做“微任务替代”,在 I/O 回调、Promise 回调、定时器回调里混用 nextTick 和 setImmediate,结果出现不可预期的执行顺序。这篇文章从实际代码出发,拆解 nextTick 队列、microtask 队列、check 阶段在事件循环各阶段的插队逻辑,并给出几个容易踩坑的真实场景和更稳妥的写法。 ## 一段跑出意外顺序的代码 出问题的代码简化后是这样的: ```js const fs = require('fs'); fs.readFile(__filename, () => { setTimeout(() => console.log('timeout'), 0); setImmediate(() => console.log('immediate')); process.nextTick(() => console.log('nextTick')); Promise.resolve().then(() => console.log('promise')); }); ``` 我的直觉是 nextTick 最优先,然后 promise,然后才是 immediate 和 timeout。实际在 Node 18 上运行,输出是: ``` nextTick promise immediate timeout ``` 这个顺序本身符合预期。真正让我困惑的是另一段在流式处理里写的代码: ```js const { Readable } = require('stream'); const source = Readable.from(['a', 'b', 'c']); source.on('data', (chunk) => { process.nextTick(() => console.log('nextTick:', chunk)); setImmediate(() => console.log('immediate:', chunk)); }); ``` 输出却是: ``` nextTick: a nextTick: b nextTick: c immediate: a immediate: b immediate: c ``` nextTick 全部先跑完,immediate 才排队执行。这不是 bug,而是 nextTick 队列在每次事件循环阶段切换前被完全清空导致的。理解这一点,需要回到事件循环的阶段划分。 ## 事件循环里 nextTick 到底插在哪里 Node 的事件循环大致分为 timers、pending callbacks、idle/prepare、poll、check、close callbacks 几个阶段。process.nextTick 不属于任何阶段,它有一个独立的队列,在每两个阶段之间、以及每个阶段内的每个回调执行完之后,都会检查并清空 nextTick 队列。也就是说 nextTick 的回调会在当前操作完成后、进入下一个阶段前被执行,而且如果 nextTick 里继续调用 nextTick,会一直执行到队列为空。 setImmediate 的回调则挂在 check 阶段。poll 阶段结束后会进入 check 阶段,此时执行 setImmediate 的回调。 Promise 的 then 回调属于 microtask,在 Node 11 之后,microtask 队列也会在每两个阶段之间以及每个回调之后被清空,和 nextTick 类似但优先级低一级。 所以一个 I/O 回调执行完后,顺序是:回调本身 → nextTick 队列清空 → microtask 队列清空 → 进入下一个阶段。这解释了上面流式处理的输出:每个 data 事件回调执行完,nextTick 队列立刻被清空,三个 chunk 的 nextTick 都在各自的回调后执行,而 setImmediate 要等到 poll 阶段结束进入 check 阶段才执行。 ## 在 I/O 回调里 nextTick 和 setImmediate 谁先 这是被问得最多的场景。结论是:在 I/O 回调内部,setImmediate 一定比 setTimeout(fn, 0) 先执行,而 nextTick 和 Promise.then 永远排在这两者之前。 ```js const fs = require('fs'); fs.readFile(__filename, () => { setTimeout(() => console.log('setTimeout'), 0); setImmediate(() => console.log('setImmediate')); }); ``` 这段代码稳定输出: ``` setImmediate setTimeout ``` 原因是在 I/O 回调里,poll 阶段还没结束,setTimeout 的计时器即使到期也至少要等到下一轮 timers 阶段,而 setImmediate 在当前的 check 阶段就会执行。很多资料说“setImmediate 和 setTimeout 顺序不确定”,那是指写在主模块顶层的情况,因为在主模块里两者的执行时机取决于进程启动耗时和定时器精度。 ```js // 主模块顶层,顺序不确定 setTimeout(() => console.log('setTimeout'), 0); setImmediate(() => console.log('setImmediate')); ``` 在主模块里,进入事件循环前会先执行同步代码,然后第一次 timers 阶段检查是否有到期的定时器。如果 1ms 的定时器已经到期,setTimeout 先执行;否则进入 poll 阶段后立刻到 check 阶段,setImmediate 先执行。所以这段代码在有的机器上输出 timeout 在前,有的机器上 immediate 在前,不能依赖。 ## 为什么用 nextTick 做递归会导致 I/O 饥饿 nextTick 队列的清空没有上限。如果你在 nextTick 回调里继续调用 nextTick,事件循环会一直卡在这个队列里,永远进不到 I/O 阶段。 ```js function loop() { process.nextTick(loop); } loop(); setTimeout(() => { console.log('this never runs'); }, 100); ``` 上面这段代码会让进程 CPU 打满,setTimeout 永远不会触发。在真实项目里,这种写法常出现在自己实现的异步队列或重试逻辑里。比如一个基于 nextTick 的任务调度器: ```js class TaskQueue { constructor() { this.tasks = []; } push(task) { this.tasks.push(task); process.nextTick(() => this.run()); } run() { const task = this.tasks.shift(); if (task) { task(); process.nextTick(() => this.run()); } } } ``` 如果任务队列里任务很多,或者某个任务里又往队列里塞任务,nextTick 就会一直占用执行权,其他定时器、I/O 回调全部被饿死。把调度换成 setImmediate 就能让出事件循环,给其他阶段执行机会: ```js class TaskQueue { constructor() { this.tasks = []; this.scheduled = false; } push(task) { this.tasks.push(task); if (!this.scheduled) { this.scheduled = true; setImmediate(() => this.run()); } } run() { this.scheduled = false; const task = this.tasks.shift(); if (task) { task(); if (this.tasks.length > 0) { this.scheduled = true; setImmediate(() => this.run()); } } } } ``` setImmediate 在每个 check 阶段只执行当前轮排队的回调,不会无限递归阻塞后续阶段,适合做批量任务的分片调度。 ## 在 HTTP 请求处理里混用两者的一次事故 有一个接口在返回响应前需要写审计日志,代码大致如下: ```js app.post('/api/order', (req, res) => { process.nextTick(() => { writeAuditLog(req.body); }); setImmediate(() => { updateOrderStats(req.body); }); res.json({ ok: true }); }); ``` 上线后发现审计日志偶尔丢失,订单统计却正常。原因是 process.nextTick 在 res.json 之前就执行完了,writeAuditLog 是异步的没有 await,报错被吞掉。这个场景其实不是 nextTick 和 setImmediate 的顺序问题,而是把 nextTick 当成了“延迟执行”的手段,但 nextTick 并不延迟,它在当前回调结束后立即执行,比 res.json 内部的异步操作还早。 如果想在响应发出后做后续处理,更可靠的是监听 finish 事件,或者用 setImmediate 把逻辑推到当前 tick 之后: ```js app.post('/api/order', (req, res) => { res.json({ ok: true }); res.on('finish', () => { setImmediate(() => { updateOrderStats(req.body).catch(err => { console.error('stats update failed', err); }); }); }); }); ``` ## 什么时候该用 nextTick,什么时候该用 setImmediate 一个实用的判断标准:nextTick 适合在当前操作完成后、事件循环继续之前做必须马上做的事情,比如在构造函数里延迟触发事件、在 API 回调返回前修正状态,这类场景要求“在当前调用栈结束后立刻执行”。setImmediate 适合把工作推到当前 I/O 轮次之后,让出执行权给其他 I/O 和定时器,比如分片处理大数组、在批量写入后触发汇总、在流式处理里延迟回调。 在 Node 里写库代码时还有一个细节:process.nextTick 在浏览器里没有等价物,如果代码需要同时跑在 Node 和浏览器环境,用 queueMicrotask 或 Promise.resolve().then 更合适。queueMicrotask 和 nextTick 在 Node 里的优先级有细微差别,nextTick 队列先于 microtask 队列清空,但绝大多数业务代码里这个差别感知不到,用 queueMicrotask 能避免绑定运行时。 ```js // 跨环境可用的延迟执行 function defer(fn) { if (typeof queueMicrotask === 'function') { queueMicrotask(fn); } else if (typeof process !== 'undefined' && process.nextTick) { process.nextTick(fn); } else { setTimeout(fn, 0); } } ``` 这段代码在 Node 和浏览器里都能用,顺序也基本符合直觉。调试事件循环问题时,在关键回调里加上 `process.nextTick(() => console.log('tick'))` 和 `setImmediate(() => console.log('check'))` 做标记,比翻文档更快定位执行阶段。 推荐查看天气:http://www.ttctq.com/zuci/243.html 推荐查看天气:http://www.ttctq.com/zuci/244.html 推荐查看天气:http://www.ttctq.com/zuci/245.html 推荐查看天气:http://www.ttctq.com/zuci/246.html 推荐查看天气:http://www.ttctq.com/zuci/247.html 推荐查看天气:http://www.ttctq.com/zuci/248.html 推荐查看天气:http://www.ttctq.com/zuci/249.html 推荐查看天气:http://www.ttctq.com/zuci/250.html 推荐查看天气:http://www.ttctq.com/zuci/251.html 推荐查看天气:http://www.ttctq.com/zuci/252.html 推荐查看天气:http://www.ttctq.com/zuci/253.html 推荐查看天气:http://www.ttctq.com/zuci/254.html 推荐查看天气:http://www.ttctq.com/zuci/255.html 推荐查看天气:http://www.ttctq.com/zuci/256.html 推荐查看天气:http://www.ttctq.com/zuci/257.html 推荐查看天气:http://www.ttctq.com/zuci/258.html 推荐查看天气:http://www.ttctq.com/zuci/259.html 推荐查看天气:http://www.ttctq.com/zuci/260.html 推荐查看天气:http://www.ttctq.com/zuci/261.html 推荐查看天气:http://www.ttctq.com/zuci/262.html 推荐查看天气:http://www.ttctq.com/zuci/263.html 推荐查看天气:http://www.ttctq.com/zuci/264.html 推荐查看天气:http://www.ttctq.com/zuci/265.html 推荐查看天气:http://www.ttctq.com/zuci/266.html 推荐查看天气:http://www.ttctq.com/zuci/267.html 推荐查看天气:http://www.ttctq.com/zuci/268.html 推荐查看天气:http://www.ttctq.com/zuci/269.html 推荐查看天气:http://www.ttctq.com/zuci/270.html 推荐查看天气:http://www.ttctq.com/zuci/271.html 推荐查看天气:http://www.ttctq.com/zuci/272.html 推荐查看天气:http://www.ttctq.com/zuci/273.html 推荐查看天气:http://www.ttctq.com/zuci/274.html 推荐查看天气:http://www.ttctq.com/zuci/275.html 推荐查看天气:http://www.ttctq.com/zuci/276.html 推荐查看天气:http://www.ttctq.com/zuci/277.html 推荐查看天气:http://www.ttctq.com/zuci/278.html 推荐查看天气:http://www.ttctq.com/zuci/279.html 推荐查看天气:http://www.ttctq.com/zuci/280.html 推荐查看天气:http://www.ttctq.com/zuci/281.html 推荐查看天气:http://www.ttctq.com/zuci/282.html 推荐查看天气:http://www.ttctq.com/zuci/283.html 推荐查看天气:http://www.ttctq.com/zuci/284.html 推荐查看天气:http://www.ttctq.com/zuci/285.html 推荐查看天气:http://www.ttctq.com/zuci/286.html 推荐查看天气:http://www.ttctq.com/zuci/287.html 推荐查看天气:http://www.ttctq.com/zuci/288.html 推荐查看天气:http://www.ttctq.com/zuci/289.html 推荐查看天气:http://www.ttctq.com/zuci/290.html 推荐查看天气:http://www.ttctq.com/zuci/291.html 推荐查看天气:http://www.ttctq.com/zuci/292.html 推荐查看天气:http://www.ttctq.com/zuci/293.html 推荐查看天气:http://www.ttctq.com/zuci/294.html 推荐查看天气:http://www.ttctq.com/zuci/295.html 推荐查看天气:http://www.ttctq.com/zuci/296.html 推荐查看天气:http://www.ttctq.com/zuci/297.html 推荐查看天气:http://www.ttctq.com/zuci/298.html 推荐查看天气:http://www.ttctq.com/zuci/299.html 推荐查看天气:http://www.ttctq.com/zuci/300.html 推荐查看天气:http://www.ttctq.com/zuci/301.html 推荐查看天气:http://www.ttctq.com/zuci/302.html 推荐查看天气:http://www.ttctq.com/zuci/303.html 推荐查看天气:http://www.ttctq.com/zuci/304.html 推荐查看天气:http://www.ttctq.com/zuci/305.html 推荐查看天气:http://www.ttctq.com/zuci/306.html 推荐查看天气:http://www.ttctq.com/zuci/307.html 推荐查看天气:http://www.ttctq.com/zuci/308.html 推荐查看天气:http://www.ttctq.com/zuci/309.html 推荐查看天气:http://www.ttctq.com/zuci/310.html 推荐查看天气:http://www.ttctq.com/zuci/311.html 推荐查看天气:http://www.ttctq.com/zuci/312.html 推荐查看天气:http://www.ttctq.com/zuci/313.html 推荐查看天气:http://www.ttctq.com/zuci/314.html 推荐查看天气:http://www.ttctq.com/zuci/315.html 推荐查看天气:http://www.ttctq.com/zuci/316.html 推荐查看天气:http://www.ttctq.com/zuci/317.html 推荐查看天气:http://www.ttctq.com/zuci/318.html 推荐查看天气:http://www.ttctq.com/zuci/319.html 推荐查看天气:http://www.ttctq.com/zuci/320.html 推荐查看天气:http://www.ttctq.com/zuci/321.html 推荐查看天气:http://www.ttctq.com/zuci/322.html 推荐查看天气:http://www.ttctq.com/zuci/323.html 推荐查看天气:http://www.ttctq.com/zuci/324.html 推荐查看天气:http://www.ttctq.com/zuci/325.html 推荐查看天气:http://www.ttctq.com/zuci/326.html 推荐查看天气:http://www.ttctq.com/zuci/327.html 推荐查看天气:http://www.ttctq.com/zuci/328.html 推荐查看天气:http://www.ttctq.com/zuci/329.html 推荐查看天气:http://www.ttctq.com/zuci/330.html 推荐查看天气:http://www.ttctq.com/zuci/331.html 推荐查看天气:http://www.ttctq.com/zuci/332.html 推荐查看天气:http://www.ttctq.com/zuci/333.html 推荐查看天气:http://www.ttctq.com/zuci/334.html 推荐查看天气:http://www.ttctq.com/zuci/335.html 推荐查看天气:http://www.ttctq.com/zuci/336.html 推荐查看天气:http://www.ttctq.com/zuci/337.html 推荐查看天气:http://www.ttctq.com/zuci/338.html 推荐查看天气:http://www.ttctq.com/zuci/339.html 推荐查看天气:http://www.ttctq.com/zuci/340.html 推荐查看天气:http://www.ttctq.com/zuci/341.html 推荐查看天气:http://www.ttctq.com/zuci/342.html 推荐查看天气:http://www.ttctq.com/zuci/343.html 推荐查看天气:http://www.ttctq.com/zuci/344.html 推荐查看天气:http://www.ttctq.com/zuci/345.html 推荐查看天气:http://www.ttctq.com/zuci/346.html 推荐查看天气:http://www.ttctq.com/zuci/347.html 推荐查看天气:http://www.ttctq.com/zuci/348.html 推荐查看天气:http://www.ttctq.com/zuci/349.html 推荐查看天气:http://www.ttctq.com/zuci/350.html 推荐查看天气:http://www.ttctq.com/zuci/351.html 推荐查看天气:http://www.ttctq.com/zuci/352.html 推荐查看天气:http://www.ttctq.com/zuci/353.html 推荐查看天气:http://www.ttctq.com/zuci/354.html 推荐查看天气:http://www.ttctq.com/zuci/355.html 推荐查看天气:http://www.ttctq.com/zuci/356.html 推荐查看天气:http://www.ttctq.com/zuci/357.html 推荐查看天气:http://www.ttctq.com/zuci/358.html 推荐查看天气:http://www.ttctq.com/zuci/359.html 推荐查看天气:http://www.ttctq.com/zuci/360.html 推荐查看天气:http://www.ttctq.com/zuci/361.html 推荐查看天气:http://www.ttctq.com/zuci/362.html 推荐查看天气:http://www.ttctq.com/zuci/363.html 推荐查看天气:http://www.ttctq.com/zuci/364.html 推荐查看天气:http://www.ttctq.com/zuci/365.html 推荐查看天气:http://www.ttctq.com/zuci/366.html 推荐查看天气:http://www.ttctq.com/zuci/367.html 推荐查看天气:http://www.ttctq.com/zuci/368.html 推荐查看天气:http://www.ttctq.com/zuci/369.html 推荐查看天气:http://www.ttctq.com/zuci/370.html 推荐查看天气:http://www.ttctq.com/zuci/371.html 推荐查看天气:http://www.ttctq.com/zuci/372.html 推荐查看天气:http://www.ttctq.com/zuci/373.html 推荐查看天气:http://www.ttctq.com/zuci/374.html 推荐查看天气:http://www.ttctq.com/zuci/375.html 推荐查看天气:http://www.ttctq.com/zuci/376.html 推荐查看天气:http://www.ttctq.com/zuci/377.html 推荐查看天气:http://www.ttctq.com/zuci/378.html 推荐查看天气:http://www.ttctq.com/zuci/379.html 推荐查看天气:http://www.ttctq.com/zuci/380.html 推荐查看天气:http://www.ttctq.com/zuci/381.html 推荐查看天气:http://www.ttctq.com/zuci/382.html 推荐查看天气:http://www.ttctq.com/zuci/383.html 推荐查看天气:http://www.ttctq.com/zuci/384.html 推荐查看天气:http://www.ttctq.com/zuci/385.html 推荐查看天气:http://www.ttctq.com/zuci/386.html 推荐查看天气:http://www.ttctq.com/zuci/387.html 推荐查看天气:http://www.ttctq.com/zuci/388.html 推荐查看天气:http://www.ttctq.com/zuci/389.html 推荐查看天气:http://www.ttctq.com/zuci/390.html 推荐查看天气:http://www.ttctq.com/zuci/391.html 推荐查看天气:http://www.ttctq.com/zuci/392.html 推荐查看天气:http://www.ttctq.com/zuci/393.html 推荐查看天气:http://www.ttctq.com/zuci/394.html 推荐查看天气:http://www.ttctq.com/zuci/395.html 推荐查看天气:http://www.ttctq.com/zuci/396.html 推荐查看天气:http://www.ttctq.com/zuci/397.html 推荐查看天气:http://www.ttctq.com/zuci/398.html 推荐查看天气:http://www.ttctq.com/zuci/399.html 推荐查看天气:http://www.ttctq.com/zuci/400.html 推荐查看天气:http://www.ttctq.com/zuci/401.html 推荐查看天气:http://www.ttctq.com/zuci/402.html 推荐查看天气:http://www.ttctq.com/zuci/403.html 推荐查看天气:http://www.ttctq.com/zuci/404.html 推荐查看天气:http://www.ttctq.com/zuci/405.html 推荐查看天气:http://www.ttctq.com/zuci/406.html 推荐查看天气:http://www.ttctq.com/zuci/407.html 推荐查看天气:http://www.ttctq.com/zuci/408.html 推荐查看天气:http://www.ttctq.com/zuci/409.html 推荐查看天气:http://www.ttctq.com/zuci/410.html 推荐查看天气:http://www.ttctq.com/zuci/411.html 推荐查看天气:http://www.ttctq.com/zuci/412.html 推荐查看天气:http://www.ttctq.com/zuci/413.html 推荐查看天气:http://www.ttctq.com/zuci/414.html 推荐查看天气:http://www.ttctq.com/zuci/415.html 推荐查看天气:http://www.ttctq.com/zuci/416.html 推荐查看天气:http://www.ttctq.com/zuci/417.html 推荐查看天气:http://www.ttctq.com/zuci/418.html 推荐查看天气:http://www.ttctq.com/zuci/419.html 推荐查看天气:http://www.ttctq.com/zuci/420.html 推荐查看天气:http://www.ttctq.com/zuci/421.html 推荐查看天气:http://www.ttctq.com/zuci/422.html 推荐查看天气:http://www.ttctq.com/zuci/423.html 推荐查看天气:http://www.ttctq.com/zuci/424.html 推荐查看天气:http://www.ttctq.com/zuci/425.html 推荐查看天气:http://www.ttctq.com/zuci/426.html 推荐查看天气:http://www.ttctq.com/zuci/427.html 推荐查看天气:http://www.ttctq.com/zuci/428.html 推荐查看天气:http://www.ttctq.com/zuci/429.html 推荐查看天气:http://www.ttctq.com/zuci/430.html 推荐查看天气:http://www.ttctq.com/zuci/431.html 推荐查看天气:http://www.ttctq.com/zuci/432.html 推荐查看天气:http://www.ttctq.com/zuci/433.html 推荐查看天气:http://www.ttctq.com/zuci/434.html 推荐查看天气:http://www.ttctq.com/zuci/435.html 推荐查看天气:http://www.ttctq.com/zuci/436.html 推荐查看天气:http://www.ttctq.com/zuci/437.html 推荐查看天气:http://www.ttctq.com/zuci/438.html 推荐查看天气:http://www.ttctq.com/zuci/439.html 推荐查看天气:http://www.ttctq.com/zuci/440.html 推荐查看天气:http://www.ttctq.com/zuci/441.html 推荐查看天气:http://www.ttctq.com/zuci/442.html

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册