Node.js中异步代码顺序调换为何引发Promise无法resolve?
Node.js连接顺序导致Promise无法resolve问题分析
问题场景
我尝试以不同顺序执行两个连接操作:
- 连接WebSocket客户端到WebSocket服务器
- 连接套接字客户端到Unix域套接字服务器
三种测试情况:
- promise1:先连WebSocket,再连Unix套接字 → 始终无法resolve
- promise2:先连Unix套接字,再连WebSocket → 正常resolve
- promise3:并行处理两个连接 → 正常resolve
测试代码:
import * as net from 'node:net'; import fs from 'fs'; import WebSocket, { WebSocketServer } from 'ws'; const testWSPort = Math.round(Math.random() * 10000); const testSocketPath = `/tmp/test-${testWSPort}.sock`; (async () => { if (fs.existsSync(testSocketPath)) { fs.unlinkSync(testSocketPath); } await startWSS({ port: testWSPort }); (await startUnixDomainSocketServer(testSocketPath)).server; console.time('wsClient'); const wsClient = new WebSocket(`ws://localhost:${testWSPort}`); console.time('socketClient'); const socketClient = net.createConnection({ path: testSocketPath }); const promise1 = new Promise<void>(async (resolve, reject) => { await new Promise<void>((done) => { wsClient.on('open', () => { done(); }); wsClient.on('error', done); }); await new Promise<void>((done) => { socketClient.on('connect', done); socketClient.on('error', done); socketClient.on('timeout', done); }); resolve(); }); const promise2 = new Promise<void>(async (resolve) => { await new Promise<void>((done) => { socketClient.on('connect', done); socketClient.on('error', done); socketClient.on('timeout', done); }); await new Promise<void>((done) => { wsClient.on('open', done); wsClient.on('error', done); }); resolve(); }); const promise3 = Promise.all([ new Promise<void>((done) => { socketClient.on('connect', () => { console.timeEnd('socketClient'); done(); }); socketClient.on('error', done); socketClient.on('timeout', done); }), new Promise<void>((done) => { wsClient.on('open', () => { console.timeEnd('wsClient'); done(); }); wsClient.on('error', done); }), ]); promise1.then(() => { console.log('Why does this line never executed?'); }); promise2.then(() => { console.log('But this line executed!'); }); promise3.then(() => { console.log('This line also executed'); }); })(); function startWSS({ port }: { port: number }) { const wss = new WebSocketServer({ port }); wss.on('connection', function connection(ws) { ws.on('error', console.error); }); return new Promise<WebSocketServer>((resolve, reject) => { wss.on('listening', () => { resolve(wss); }); wss.on('error', reject); }); } function startUnixDomainSocketServer( socketPath: string ): Promise<{ server: net.Server }> { return new Promise((resolve, reject) => { const server = new net.Server(); server.listen(socketPath, () => { resolve({ server }); }); server.on('error', (err) => { reject(err); }); }); }
核心问题
- 为什么promise1始终无法resolve?
- 为何调换连接顺序会产生这种差异?
问题分析
1. promise1无法resolve的原因
核心问题是事件绑定时机晚于事件触发时机:
- Unix域套接字的连接建立速度远快于WebSocket(前者是本地IPC,无TCP握手;后者需要完成HTTP握手流程)。
- 创建
socketClient后,它会立即发起连接,且连接会在极短时间内完成,connect事件会在当前同步代码执行完毕后进入事件队列等待处理。 - 在
promise1的逻辑中,你先等待WebSocket的open事件完成,之后才给socketClient绑定connect事件。但此时socketClient的connect事件已经触发过了——Node.js的事件是一次性的(触发后不会重复发射),新绑定的回调永远不会被调用,导致第二个await的Promise永远处于pending状态,promise1也就无法resolve。
2. 调换顺序后正常的原因
在promise2中,你先给socketClient绑定connect事件:
- 此时
socketClient的连接尚未完成(同步代码还在执行,事件队列未处理),绑定的回调能正常捕获到后续触发的connect事件。 - 当
socketClient的连接完成后,再给wsClient绑定open事件——此时WebSocket的握手流程还未完成,open事件尚未触发,因此后续能正常捕获到该事件,最终promise2可以正常resolve。
补充:promise3正常的原因
promise3通过Promise.all并行绑定两个事件:
- 在同步代码阶段就完成了两个客户端的事件绑定,此时两个连接都尚未完成,后续事件触发时都能被对应的回调捕获,因此可以正常resolve。
内容的提问来源于stack exchange,提问作者Shu Chang
相关产品推荐
相关产品推荐

