Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions apps/site/navigation.json
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,10 @@
"link": "/learn/asynchronous-work/javascript-asynchronous-programming-and-callbacks",
"label": "components.navigation.learn.asynchronousWork.links.javascriptAsynchronousProgrammingAndCallbacks"
},
"discoverPromisesInNodejs": {
"link": "/learn/asynchronous-work/discover-promises-in-nodejs",
"label": "components.navigation.learn.asynchronousWork.links.discoverPromisesInNodejs"
},
"discoverJavascriptTimers": {
"link": "/learn/asynchronous-work/discover-javascript-timers",
"label": "components.navigation.learn.asynchronousWork.links.discoverJavascriptTimers"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
---
title: Discover Promises in Node.js
layout: learn
authors: avivkeller
---

# Discover Promises in Node.js

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.

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."
Comment thread
avivkeller marked this conversation as resolved.

## Promise States

A Promise can be in one of three states:

- **Pending**: The initial state, where the asynchronous operation is still running.
- **Fulfilled**: The operation completed successfully, and the Promise is now resolved with a value.
- **Rejected**: The operation failed, and the Promise is settled with a reason (usually an error).

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.
Comment thread
avivkeller marked this conversation as resolved.
Outdated

## Basic Syntax of a Promise

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**.
Comment thread
avivkeller marked this conversation as resolved.
Outdated

```js
const myPromise = new Promise((resolve, reject) => {
let success = true;

if (success) {
resolve('Operation was successful!');
} else {
reject('Something went wrong.');
}
});
Comment thread
avivkeller marked this conversation as resolved.
```

In the above example:

- If the `success` condition is `true`, the Promise is fulfilled and the value `'Operation was successful!'` is passed to the `resolve` function.
- If the `success` condition is `false`, the Promise is rejected and the error `'Something went wrong.'` is passed to the `reject` function.

## Handling Promises with `.then()` and `.catch()`

Once a Promise is created, you can handle the outcome by using the `.then()` and `.catch()` methods.

- `.then()` is used to handle a fulfilled Promise and access its result.
- `.catch()` is used to handle a rejected Promise and catch any errors that may occur.

```js
const myPromise = new Promise((resolve, reject) => {
let success = true;

if (success) {
resolve('Operation was successful!');
} else {
reject('Something went wrong.');
}
});

myPromise
.then(result => {
console.log(result); // This will run if the Promise is fulfilled
})
.catch(error => {
console.error(error); // This will run if the Promise is rejected
});
```
Comment thread
avivkeller marked this conversation as resolved.

## Chaining Promises

One of the great features of Promises is that they allow you to chain multiple asynchronous operations together. When you chain Promises, each `.then()` block waits for the previous one to complete before it runs.

```js
const { setTimeout: delay } = require('node:timers/promises');

const promise1 = delay(1000).then(() => 'First task completed');
const promise2 = delay(1000).then(() => 'Second task completed');
Comment thread
avivkeller marked this conversation as resolved.
Outdated

promise1
.then(result => {
console.log(result); // 'First task completed'
return promise2; // Return the second Promise
})
.then(result => {
console.log(result); // 'Second task completed'
})
.catch(error => {
console.error(error); // If any Promise is rejected, catch the error
});
```

## Using Async/Await with Promises

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.

- `async` is used to define a function that returns a Promise.
- `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.
Comment thread
avivkeller marked this conversation as resolved.
Outdated

```js
async function performTasks() {
try {
const result1 = await promise1;
console.log(result1); // 'First task completed'

const result2 = await promise2;
console.log(result2); // 'Second task completed'
} catch (error) {
console.error(error); // Catches any rejection or error
}
}

performTasks();
```

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.
Comment thread
avivkeller marked this conversation as resolved.
Outdated

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.
Comment thread
avivkeller marked this conversation as resolved.

## Promise-based Node.js APIs

Node.js provides **Promise-based versions** of many of its core APIs, especially in cases where asynchronous operations were traditionally handled with callbacks. This makes it easier to work with Node.js APIs and Promises, and reduces the risk of "callback hell."

For example, the `fs` (file system) module has a Promise-based API under `fs.promises`:

```js
const fs = require('node:fs').promises;
// Or, you can import the promisified version directly:
// const fs = require('node:fs/promises');

async function readFile() {
try {
const data = await fs.readFile('example.txt', 'utf8');
console.log(data);
} catch (err) {
console.error('Error reading file:', err);
}
}

readFile();
```

In this example, `fs.promises.readFile()` returns a Promise, which we handle using `async/await` syntax to read the contents of a file asynchronously.

## Advanced Promise Methods

JavaScript's `Promise` global provides several powerful methods that help manage multiple asynchronous tasks more effectively:

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

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.
Comment thread
avivkeller marked this conversation as resolved.
Outdated

`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.
Comment thread
avivkeller marked this conversation as resolved.
Outdated

```js
const { setTimeout: delay } = require('node:timers/promises');

const fetchData1 = delay(1000).then(() => 'Data from API 1');
const fetchData2 = delay(2000).then(() => 'Data from API 2');

Promise.all([fetchData1, fetchData2])
.then(results => {
console.log(results); // ['Data from API 1', 'Data from API 2']
})
.catch(error => {
console.error('Error:', error);
});
```

### **`Promise.allSettled()`**:

This method waits for all promises to either resolve or reject and returns an array of objects that describe the outcome of each Promise.

```js
const promise1 = Promise.resolve('Success');
const promise2 = Promise.reject('Failed');

Promise.allSettled([promise1, promise2]).then(results => {
console.log(results);
// [ { status: 'fulfilled', value: 'Success' }, { status: 'rejected', reason: 'Failed' } ]
});
```

Unlike `Promise.all()`, `Promise.allSettled()` does not short-circuit on failure. It waits for all promises to settle, even if some reject. This provides better error handling for batch operations, where you may want to know the status of all tasks, regardless of failure.

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

This method resolves or rejects as soon as the first Promise settles, whether it resolves or rejects.

```js
const { setTimeout: delay } = require('node:timers/promises');

const task1 = delay(2000).then(() => 'Task 1 done');
const task2 = delay(1000).then(() => 'Task 2 done');

Promise.race([task1, task2]).then(result => {
console.log(result); // 'Task 2 done' (since task2 finishes first)
});
```
Comment thread
avivkeller marked this conversation as resolved.

### **`Promise.any()`**:

This method resolves as soon as one of the Promises resolves. If all promises are rejected, it will reject with an `AggregateError`.

```js
const { setTimeout: delay } = require('node:timers/promises');

const api1 = delay(2000).then(() => 'API 1 failed');
const api2 = delay(1000).then(() => 'API 2 success');
Comment thread
avivkeller marked this conversation as resolved.

Promise.any([api1, api2])
.then(result => {
console.log(result); // 'API 2 success' (since it resolves first)
})
.catch(error => {
console.error('All promises rejected:', error);
});
```

### **`Promise.reject()` and `Promise.resolve()`**:

These methods create a rejected or resolved Promise directly.

```js
Promise.resolve('Resolved immediately').then(result => {
console.log(result); // 'Resolved immediately'
});
```
Comment thread
avivkeller marked this conversation as resolved.

## Error Handling with Promises

Handling errors in Promises ensures your application behaves correctly in case of unexpected situations.

- You can use `.catch()` to handle any errors or rejections that occur during the execution of Promises.

```js
myPromise
.then(result => console.log(result))
.catch(error => console.error(error)); // Handles the rejection
```

- Alternatively, when using `async/await`, you can use a `try/catch` block to catch and handle errors.

```js
async function performTask() {
try {
const result = await myPromise;
console.log(result);
} catch (error) {
console.error(error); // Handles any errors
} finally {
// This code is executed regardless of failure
console.log('performTask() completed');
}
}

performTask();
```

## Scheduling Tasks in the Event Loop

In addition to Promises, Node.js provides several other mechanisms for scheduling tasks in the event loop.

### **`queueMicrotask()`**

`queueMicrotask()` is used to schedule a microtask, which is a lightweight task that runs after the currently executing script but before any other I/O events or timers. Microtasks include tasks like Promise resolutions and other asynchronous operations that are prioritized over regular tasks.

```js
queueMicrotask(() => {
console.log('Microtask is executed');
});

console.log('Synchronous task is executed');
```

In the above example, "Microtask is executed" will be logged after "Synchronous task is executed," but before any I/O operations like timers.

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

`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.
Comment thread
avivkeller marked this conversation as resolved.
Outdated

```js
process.nextTick(() => {
console.log('Next tick callback');
});

console.log('Synchronous task executed');
```

### **`setImmediate()`**:

`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.
Comment thread
avivkeller marked this conversation as resolved.
Outdated

```js
setImmediate(() => {
console.log('Immediate callback');
});

console.log('Synchronous task executed');
```

### **When to Use Each**:
Comment thread
avivkeller marked this conversation as resolved.
Outdated

- Use `queueMicrotask()` for tasks that need to run immediately after the current script and before any I/O or timer callbacks, typically for Promise resolutions.
- Use `process.nextTick()` for tasks that should execute before any I/O events, often useful for deferring operations or handling errors synchronously.
Comment thread
avivkeller marked this conversation as resolved.
- Use `setImmediate()` for tasks that should run after I/O events but before timers.

In short, the execution order is as follows:
Comment thread
avivkeller marked this conversation as resolved.
Outdated

1. **Synchronous code** (e.g., regular function calls and script code)
2. **`process.nextTick()`** callbacks
3. **Microtasks** (e.g., Promises, `queueMicrotask()`)
4. **Timers** (e.g., `setTimeout()`, `setInterval()`)
5. **I/O callbacks** (e.g., network and file system operations)
6. **`setImmediate()`** callbacks
7. **Close callbacks** (e.g., `socket.on('close')`)
Comment thread
avivkeller marked this conversation as resolved.
Outdated
1 change: 1 addition & 0 deletions packages/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"links": {
"asynchronousWork": "Asynchronous Work",
"asynchronousFlowControl": "Asynchronous flow control",
"discoverPromisesInNodejs": "Discover Promises in Node.js",
"overviewOfBlockingVsNonBlocking": "Overview of Blocking vs Non-Blocking",
"javascriptAsynchronousProgrammingAndCallbacks": "JavaScript Asynchronous Programming and Callbacks",
"discoverJavascriptTimers": "Discover JavaScript Timers",
Expand Down
Loading