@@ -1489,64 +1489,75 @@ foo(ee, 'foo', ac.signal); |
1489 | 1489 | ac.abort(); // Prints: Waiting for the event was canceled! |
1490 | 1490 | ``` |
1491 | 1491 |
|
1492 | | -### Awaiting multiple events emitted on `process.nextTick()` |
| 1492 | +### Caveats when awaiting multiple events |
1493 | 1493 |
|
1494 | | -There is an edge case worth noting when using the `events.once()` function |
1495 | | -to await multiple events emitted on in the same batch of `process.nextTick()` |
1496 | | -operations, or whenever multiple events are emitted synchronously. Specifically, |
1497 | | -because the `process.nextTick()` queue is drained before the `Promise` microtask |
1498 | | -queue, and because `EventEmitter` emits all events synchronously, it is possible |
1499 | | -for `events.once()` to miss an event. |
| 1494 | +It is important to be aware of execution order when using the `events.once()` |
| 1495 | +method to await multiple events. |
| 1496 | + |
| 1497 | +Conventional event listeners are called synchronously when the event is |
| 1498 | +emitted. This guarantees that execution will not proceed beyond the emitted |
| 1499 | +event until all listeners have finished executing. |
| 1500 | + |
| 1501 | +The same is _not_ true when awaiting Promises returned by `events.once()`. |
| 1502 | +Promise tasks are not handled until after the current execution stack runs to |
| 1503 | +completion, which means that multiple events could be emitted before |
| 1504 | +asynchronous execution continues from the relevant `await` statement. |
| 1505 | + |
| 1506 | +As a result, events can be "missed" if a series of `await events.once()` |
| 1507 | +statements is used to listen to multiple events, since there might be times |
| 1508 | +where more than one event is emitted during the same phase of the event loop. |
| 1509 | +(The same is true when using `process.nextTick()` to emit events, because the |
| 1510 | +tasks queued by `process.nextTick()` are executed before Promise tasks.) |
1500 | 1511 |
|
1501 | 1512 | ```mjs |
1502 | 1513 | import { EventEmitter, once } from 'node:events'; |
1503 | 1514 | import process from 'node:process'; |
1504 | 1515 |
|
1505 | 1516 | const myEE = new EventEmitter(); |
1506 | 1517 |
|
1507 | | -async function foo() { |
1508 | | - await once(myEE, 'bar'); |
1509 | | - console.log('bar'); |
1510 | | - |
1511 | | - // This Promise will never resolve because the 'foo' event will |
1512 | | - // have already been emitted before the Promise is created. |
| 1518 | +async function listen() { |
1513 | 1519 | await once(myEE, 'foo'); |
1514 | 1520 | console.log('foo'); |
| 1521 | + |
| 1522 | + // This Promise will never resolve, because the 'bar' event will |
| 1523 | + // have already been emitted before the next line is executed. |
| 1524 | + await once(myEE, 'bar'); |
| 1525 | + console.log('bar'); |
1515 | 1526 | } |
1516 | 1527 |
|
1517 | 1528 | process.nextTick(() => { |
1518 | | - myEE.emit('bar'); |
1519 | 1529 | myEE.emit('foo'); |
| 1530 | + myEE.emit('bar'); |
1520 | 1531 | }); |
1521 | 1532 |
|
1522 | | -foo().then(() => console.log('done')); |
| 1533 | +listen().then(() => console.log('done')); |
1523 | 1534 | ``` |
1524 | 1535 |
|
1525 | 1536 | ```cjs |
1526 | 1537 | const { EventEmitter, once } = require('node:events'); |
1527 | 1538 |
|
1528 | 1539 | const myEE = new EventEmitter(); |
1529 | 1540 |
|
1530 | | -async function foo() { |
1531 | | - await once(myEE, 'bar'); |
1532 | | - console.log('bar'); |
1533 | | - |
1534 | | - // This Promise will never resolve because the 'foo' event will |
1535 | | - // have already been emitted before the Promise is created. |
| 1541 | +async function listen() { |
1536 | 1542 | await once(myEE, 'foo'); |
1537 | 1543 | console.log('foo'); |
| 1544 | + |
| 1545 | + // This Promise will never resolve, because the 'bar' event will |
| 1546 | + // have already been emitted before the next line is executed. |
| 1547 | + await once(myEE, 'bar'); |
| 1548 | + console.log('bar'); |
1538 | 1549 | } |
1539 | 1550 |
|
1540 | 1551 | process.nextTick(() => { |
1541 | | - myEE.emit('bar'); |
1542 | 1552 | myEE.emit('foo'); |
| 1553 | + myEE.emit('bar'); |
1543 | 1554 | }); |
1544 | 1555 |
|
1545 | | -foo().then(() => console.log('done')); |
| 1556 | +listen().then(() => console.log('done')); |
1546 | 1557 | ``` |
1547 | 1558 |
|
1548 | | -To catch both events, create each of the Promises _before_ awaiting either |
1549 | | -of them, then it becomes possible to use `Promise.all()`, `Promise.race()`, |
| 1559 | +To catch multiple events, create all of the Promises _before_ awaiting any of |
| 1560 | +them. This is usually made easier by using `Promise.all()`, `Promise.race()`, |
1550 | 1561 | or `Promise.allSettled()`: |
1551 | 1562 |
|
1552 | 1563 | ```mjs |
@@ -1555,35 +1566,41 @@ import process from 'node:process'; |
1555 | 1566 |
|
1556 | 1567 | const myEE = new EventEmitter(); |
1557 | 1568 |
|
1558 | | -async function foo() { |
1559 | | - await Promise.all([once(myEE, 'bar'), once(myEE, 'foo')]); |
| 1569 | +async function listen() { |
| 1570 | + await Promise.all([ |
| 1571 | + once(myEE, 'foo'), |
| 1572 | + once(myEE, 'bar'), |
| 1573 | + ]); |
1560 | 1574 | console.log('foo', 'bar'); |
1561 | 1575 | } |
1562 | 1576 |
|
1563 | 1577 | process.nextTick(() => { |
1564 | | - myEE.emit('bar'); |
1565 | 1578 | myEE.emit('foo'); |
| 1579 | + myEE.emit('bar'); |
1566 | 1580 | }); |
1567 | 1581 |
|
1568 | | -foo().then(() => console.log('done')); |
| 1582 | +listen().then(() => console.log('done')); |
1569 | 1583 | ``` |
1570 | 1584 |
|
1571 | 1585 | ```cjs |
1572 | 1586 | const { EventEmitter, once } = require('node:events'); |
1573 | 1587 |
|
1574 | 1588 | const myEE = new EventEmitter(); |
1575 | 1589 |
|
1576 | | -async function foo() { |
1577 | | - await Promise.all([once(myEE, 'bar'), once(myEE, 'foo')]); |
| 1590 | +async function listen() { |
| 1591 | + await Promise.all([ |
| 1592 | + once(myEE, 'bar'), |
| 1593 | + once(myEE, 'foo'), |
| 1594 | + ]); |
1578 | 1595 | console.log('foo', 'bar'); |
1579 | 1596 | } |
1580 | 1597 |
|
1581 | 1598 | process.nextTick(() => { |
1582 | | - myEE.emit('bar'); |
1583 | 1599 | myEE.emit('foo'); |
| 1600 | + myEE.emit('bar'); |
1584 | 1601 | }); |
1585 | 1602 |
|
1586 | | -foo().then(() => console.log('done')); |
| 1603 | +listen().then(() => console.log('done')); |
1587 | 1604 | ``` |
1588 | 1605 |
|
1589 | 1606 | ## `events.captureRejections` |
|