Skip to content

Commit 0a1705c

Browse files
committed
code review
1 parent 3bcb1d2 commit 0a1705c

File tree

1 file changed

+87
-21
lines changed

1 file changed

+87
-21
lines changed

apps/site/pages/en/learn/asynchronous-work/discover-promises-in-nodejs.md

Lines changed: 87 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ authors: avivkeller
66

77
# Discover Promises in Node.js
88

9-
A **Promise** is a special object in JavaScript that represents the eventual completion (or failure) of an asynchronous operation and its resulting value. Think of a Promise as a placeholder for a value that is not yet available but will be in the future.
9+
A **Promise** is a special object in JavaScript that represents the eventual completion (or failure) of an asynchronous operation and its resulting value. Essentially, a Promise is a placeholder for a value that is not yet available but will be in the future.
1010

1111
Think of a Promise like ordering a pizza: you don't get it right away, but the delivery person promises to bring it to you later. You don't know _exactly_ when, but you know the outcome will either be "pizza delivered" or "something went wrong."
1212

@@ -18,11 +18,16 @@ A Promise can be in one of three states:
1818
- **Fulfilled**: The operation completed successfully, and the Promise is now resolved with a value.
1919
- **Rejected**: The operation failed, and the Promise is settled with a reason (usually an error).
2020

21-
For example, imagine you're waiting for an email from a friend. You're in the pending state. If your friend replies, you've entered the fulfilled state. If the email bounces back, you are in the rejected state.
21+
When you order the pizza, You're in the pending state, hungry and hopeful. If the pizza arrives hot and cheesy, you've entered the fulfilled state. But if the restaurant calls to say they've dropped your pizza on floor, you're in the rejected state.
22+
23+
Regardless of whether your dinner ends in joy or disappointment, once there's a final outcome, the Promise is considered **settled**.
2224

2325
## Basic Syntax of a Promise
2426

25-
A Promise is created using the `new Promise()` constructor. The constructor takes a function with two parameters: `resolve` and `reject`. These functions are used to transition the Promise from the **pending** state to either **fulfilled** or **rejected**.
27+
One of the most common ways to create a Promise is using the `new Promise()` constructor. The constructor takes a function with two parameters: `resolve` and `reject`. These functions are used to transition the Promise from the **pending** state to either **fulfilled** or **rejected**.
28+
29+
If an error is thrown inside the executor function, the Promise will be rejected with that error.
30+
The return value of the executor function is ignored: only `resolve` or `reject` should be used to settle the Promise.
2631

2732
```js
2833
const myPromise = new Promise((resolve, reject) => {
@@ -41,12 +46,13 @@ In the above example:
4146
- If the `success` condition is `true`, the Promise is fulfilled and the value `'Operation was successful!'` is passed to the `resolve` function.
4247
- If the `success` condition is `false`, the Promise is rejected and the error `'Something went wrong.'` is passed to the `reject` function.
4348

44-
## Handling Promises with `.then()` and `.catch()`
49+
## Handling Promises with `.then()`, `.catch()`, and `.finally()`
4550

46-
Once a Promise is created, you can handle the outcome by using the `.then()` and `.catch()` methods.
51+
Once a Promise is created, you can handle the outcome by using the `.then()`, `.catch()`, and `.finally()` methods.
4752

4853
- `.then()` is used to handle a fulfilled Promise and access its result.
4954
- `.catch()` is used to handle a rejected Promise and catch any errors that may occur.
55+
- `.finally()` is used to handle a settled Promise, regardless of whether the Promise resolved or rejected.
5056

5157
```js
5258
const myPromise = new Promise((resolve, reject) => {
@@ -65,6 +71,9 @@ myPromise
6571
})
6672
.catch(error => {
6773
console.error(error); // This will run if the Promise is rejected
74+
})
75+
.finally(() => {
76+
console.log('The promise has completed'); // This will run when the Promise is settled
6877
});
6978
```
7079

@@ -75,13 +84,12 @@ One of the great features of Promises is that they allow you to chain multiple a
7584
```js
7685
const { setTimeout: delay } = require('node:timers/promises');
7786

78-
const promise1 = delay(1000).then(() => 'First task completed');
79-
const promise2 = delay(1000).then(() => 'Second task completed');
87+
const promise = delay(1000).then(() => 'First task completed');
8088

81-
promise1
89+
promise
8290
.then(result => {
8391
console.log(result); // 'First task completed'
84-
return promise2; // Return the second Promise
92+
return delay(1000).then(() => 'Second task completed'); // Return a second Promise
8593
})
8694
.then(result => {
8795
console.log(result); // 'Second task completed'
@@ -96,7 +104,7 @@ promise1
96104
One of the best ways to work with Promises in modern JavaScript is using **async/await**. This allows you to write asynchronous code that looks synchronous, making it much easier to read and maintain.
97105

98106
- `async` is used to define a function that returns a Promise.
99-
- `await` is used inside an `async` function to pause execution until a Promise settles. However, in [ECMAScript Modules](https://nodejs.org/api/esm.html), you can use [`await` at the top level](https://nodejs.org/api/esm.html#top-level-await) without needing an `async` function.
107+
- `await` is used inside an `async` function to pause execution until a Promise settles.
100108

101109
```js
102110
async function performTasks() {
@@ -114,7 +122,33 @@ async function performTasks() {
114122
performTasks();
115123
```
116124

117-
In the `performTasks` function, the `await` keyword pauses execution until each Promise is settled (resolved or rejected). This leads to a more linear and readable flow of asynchronous code.
125+
In the `performTasks` function, the `await` keyword ensures that each Promise is settled before moving on to the next statement. This leads to a more linear and readable flow of asynchronous code.
126+
127+
Essentially, the code above will execute the same as if the user wrote:
128+
129+
```js
130+
promise1
131+
.then(function (result1) {
132+
console.log(result1);
133+
return promise2;
134+
})
135+
.then(function (result2) {
136+
console.log(result2);
137+
})
138+
.catch(function (error) {
139+
console.log(error);
140+
});
141+
```
142+
143+
### Top-Level Await
144+
145+
When using [ECMAScript Modules](https://nodejs.org/api/esm.html), the module itself is treated as a top-level scope that supports asynchronous operations natively. This means that you can use [`await` at the top level](https://nodejs.org/api/esm.html#top-level-await) without needing an `async` function.
146+
147+
```mjs
148+
import { setTimeout as delay } from 'node:timers/promises';
149+
150+
await delay(1000);
151+
```
118152

119153
Async/await can be much more intricate than the simple examples provided. James Snell, a member of the Node.js Technical Steering Committee, has an [in-depth presentation](https://www.youtube.com/watch?v=XV-u_Ow47s0) that explores the complexities of Promises and async/await.
120154

@@ -149,9 +183,7 @@ JavaScript's `Promise` global provides several powerful methods that help manage
149183

150184
### **`Promise.all()`**:
151185

152-
This method takes an array of Promises and resolves them all. It only resolves when all Promises are fulfilled. If any of the Promises is rejected, `Promise.all()` will reject immediately.
153-
154-
`Promise.all()` resolves when all Promises are completed. If you have a large number of Promises, especially in situations like batch processing, it could overwhelm the system's memory.
186+
This method accepts an array of Promises and returns a new Promise that resolves once all the Promises are fulfilled. If any Promise is rejected, `Promise.all()` will immediately reject. However, even if rejection occurs, the Promises continue to execute. When handling a large number of Promises, especially in batch processing, using this function can strain the system's memory.
155187

156188
```js
157189
const { setTimeout: delay } = require('node:timers/promises');
@@ -186,7 +218,7 @@ Unlike `Promise.all()`, `Promise.allSettled()` does not short-circuit on failure
186218

187219
### **`Promise.race()`**:
188220

189-
This method resolves or rejects as soon as the first Promise settles, whether it resolves or rejects.
221+
This method resolves or rejects as soon as the first Promise settles, whether it resolves or rejects. Regardless of which promise settles first, all promises are fully executed.
190222

191223
```js
192224
const { setTimeout: delay } = require('node:timers/promises');
@@ -206,10 +238,11 @@ This method resolves as soon as one of the Promises resolves. If all promises ar
206238
```js
207239
const { setTimeout: delay } = require('node:timers/promises');
208240

209-
const api1 = delay(2000).then(() => 'API 1 failed');
241+
const api1 = delay(2000).then(() => 'API 1 success');
210242
const api2 = delay(1000).then(() => 'API 2 success');
243+
const api3 = delay(1500).then(() => 'API 3 success');
211244

212-
Promise.any([api1, api2])
245+
Promise.any([api1, api2, api3])
213246
.then(result => {
214247
console.log(result); // 'API 2 success' (since it resolves first)
215248
})
@@ -228,6 +261,31 @@ Promise.resolve('Resolved immediately').then(result => {
228261
});
229262
```
230263

264+
### **`Promise.try()`**
265+
266+
`Promise.try()` is a method that executes a given function, whether it's synchronous or asynchronous, and wraps the result in a promise. If the function throws an error or returns a rejected promise, `Promise.try()` will return a rejected promise. If the function completes successfully, the returned promise will be fulfilled with its value.
267+
268+
This can be particularly useful for starting promise chains in a consistent way, especially when working with code that might throw errors synchronously.
269+
270+
```js
271+
function mightThrow() {
272+
if (Math.random() > 0.5) {
273+
throw new Error('Oops, something went wrong!');
274+
}
275+
return 'Success!';
276+
}
277+
278+
Promise.try(mightThrow)
279+
.then(result => {
280+
console.log('Result:', result);
281+
})
282+
.catch(err => {
283+
console.error('Caught error:', err.message);
284+
});
285+
```
286+
287+
In this example, `Promise.try()` ensures that if `mightThrow()` throws an error, it will be caught in the `.catch()` block, making it easier to handle both sync and async errors in one place.
288+
231289
## Error Handling with Promises
232290

233291
Handling errors in Promises ensures your application behaves correctly in case of unexpected situations.
@@ -237,7 +295,8 @@ Handling errors in Promises ensures your application behaves correctly in case o
237295
```js
238296
myPromise
239297
.then(result => console.log(result))
240-
.catch(error => console.error(error)); // Handles the rejection
298+
.catch(error => console.error(error)) // Handles the rejection
299+
.finally(error => console.log('Promise completed')); // Runs regardless of promise resolution
241300
```
242301

243302
- Alternatively, when using `async/await`, you can use a `try/catch` block to catch and handle errors.
@@ -278,7 +337,7 @@ In the above example, "Microtask is executed" will be logged after "Synchronous
278337

279338
### **`process.nextTick()`**
280339

281-
`process.nextTick()` is used to schedule a callback to be executed after the current operation completes, but before any other event loop phases, such as I/O events, timers, or Promises. This is useful for situations where you want to ensure that a callback is executed as soon as possible, but still after the current execution context.
340+
`process.nextTick()` is used to schedule a callback to be executed immediately after the current operation completes. This is useful for situations where you want to ensure that a callback is executed as soon as possible, but still after the current execution context.
282341

283342
```js
284343
process.nextTick(() => {
@@ -290,7 +349,7 @@ console.log('Synchronous task executed');
290349

291350
### **`setImmediate()`**:
292351

293-
`setImmediate()` is used to execute a callback after the current event loop cycle finishes and all I/O events have been processed. This means that `setImmediate()` callbacks run after any I/O callbacks, but before timers and the next tick queue.
352+
`setImmediate()` is used to execute a callback after the current event loop cycle finishes and all I/O events have been processed. This means that `setImmediate()` callbacks run after any I/O callbacks, but before timers.
294353

295354
```js
296355
setImmediate(() => {
@@ -306,12 +365,19 @@ console.log('Synchronous task executed');
306365
- Use `process.nextTick()` for tasks that should execute before any I/O events, often useful for deferring operations or handling errors synchronously.
307366
- Use `setImmediate()` for tasks that should run after I/O events but before timers.
308367

368+
Because these tasks execute outside of the current synchronous flow, uncaught exceptions inside these callbacks won't be caught by surrounding `try/catch` blocks and may crash the application if not properly managed (e.g., by attaching `.catch()` to Promises or using global error handlers like `process.on('uncaughtException')`).
369+
309370
In short, the execution order is as follows:
310371

311372
1. **Synchronous code** (e.g., regular function calls and script code)
312373
2. **`process.nextTick()`** callbacks
313374
3. **Microtasks** (e.g., Promises, `queueMicrotask()`)
375+
If a microtask queues another microtask, the new one is also executed before moving on.
314376
4. **Timers** (e.g., `setTimeout()`, `setInterval()`)
315377
5. **I/O callbacks** (e.g., network and file system operations)
378+
Some operations, like `close` events, may use `process.nextTick()` internally and are executed in the next tick.
316379
6. **`setImmediate()`** callbacks
317-
7. **Close callbacks** (e.g., `socket.on('close')`)
380+
381+
`process.nextTick()` can execute at any stage in the execution order, as it is not bound by the event loop.
382+
383+
Note that this order, and the `process.nextTick()` functionality, is specific to Node.js, and other runtimes may handle different events at different times.

0 commit comments

Comments
 (0)