Home Web Front-end JS Tutorial Create your own Promise in JavaScript

Create your own Promise in JavaScript

Dec 28, 2024 pm 01:29 PM

Create your own Promise in JavaScript

Why?

To get some idea how JavaScript Promises run callbacks asynchronously under the hood.

Let's create our own Promise in JavaScript! We'll follow the Promise/A specification, which outlines how promises handle async operations, resolve, reject, and ensure predictable chaining and error handling.

To keep things simple, we'll focus on the key rules marked by ✅ in the Promises/A specification. This won't be a full implementation, but a simplified version. Here's what we'll build:

1. Terminology

1.1 'promise' is an object or function with a then method whose behavior conforms to this specification.

1.2 thenable' is an object or function that defines a then method.

1.3 'value' is any legal JavaScript value (including undefined, a thenable, or a promise).

1.4 'exception' is a value that is thrown using the throw statement.

1.5 'reason' is a value that indicates why a promise was rejected.

2. Requirements

2.1 Promise States

A promise must be in one of three states: pending, fulfilled, or rejected.

2.1.1. When pending, a promise: ✅

⟶ may transition to either the fulfilled or rejected state.

2.1.2. When fulfilled, a promise: ✅

⟶ must not transition to any other state.

⟶ must have a value, which must not change.

2.1.3. When rejected, a promise: ✅

⟶ must not transition to any other state.

⟶ must have a reason, which must not change.

2.2 The then Method

A promise must provide a then method to access its current or eventual value or reason.

A promise's then method accepts two arguments:

2.2.1. Both onFulfilled and onRejected are optional arguments: ✅

⟶ If onFulfilled is not a function, it must be ignored.

⟶ If onRejected is not a function, it must be ignored.

2.2.2. If onFulfilled is a function: ✅

⟶ it must be called after promise is fulfilled, with promise's value as its first argument.

⟶ it must not be called before promise is fulfilled.

⟶ it must not be called more than once.

2.2.3. If onRejected is a function, ✅

⟶ it must be called after promise is rejected, with promise's reason as its first argument.

⟶ it must not be called before promise is rejected.

⟶ it must not be called more than once.

2.2.4. onFulfilled or onRejected must not be called until the execution context stack contains only platform code. ✅

2.2.5. onFulfilled and onRejected must be called as functions (i.e. with no this value). ✅

2.2.6. then may be called multiple times on the same promise. ✅

⟶ If/when promise is fulfilled, all respective onFulfilled callbacks must execute in the order of their originating calls to then.

⟶ If/when promise is rejected, all respective onRejected callbacks must execute in the order of their originating calls to then.

2.2.7. then must return a promise. ✅

⟶ If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x). ❌

⟶ If either onFulfilled or onRejected throws an exception e, promise2 must be rejected with e as the reason. ❌

⟶ If onFulfilled is not a function and promise1 is fulfilled, promise2 must be fulfilled with the same value as promise1. ❌

⟶ If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason as promise1. ❌

Implementation

A JavaScript Promise takes an executor function as an argument, which is called immediately when the Promise is created:

The core Promises/A specification does not deal with how to create, fulfill, or reject promises. It's up to you. But the implementation you provide for the promise's construction has to be compatible with asynchronous APIs in JavaScript. Here is the first draft of our Promise class:

Rule 2.1 (Promise States) states that a promise must be in one of three states: pending, fulfilled, or rejected. It also explains what happens in each of these states.

When fulfilled or rejected, a promise must not transition to any other state. Therefore, we need to ensure the promise is in the pending state before making any transition:

We already know that a promise's initial state is pending, and we ensure it remains so until explicitly fulfilled or rejected:

Since the executor function is called immediately upon the promise's instantiation, we invoke it within the constructor method:

Our first draft of the YourPromise class is done here.

The Promise/A specification mostly focuses on defining an interoperable then() method. This method lets us access the promise's current or eventual value or reason. Let's dive into it.

Rule 2.2 (The then Method) states that a promise must have a then() method, which accepts two arguments:

Both onFulfilled and onRejected must be called after the promise is fulfilled or rejected, passing the promise's value or reason as their first argument if they are functions:

Additionally, they must not be called before the promise is fulfilled or rejected, nor more than once. Both onFulfilled and onRejected are optional and should be ignored if they are not functions.

If you look at Rules 2.2, 2.2.6, and 2.2.7, you'll see that a promise must have a then() method, the then() method can be called multiple times, and it must return a promise:

To keep things simple, we won't deal with separate classes or functions. We'll return a promise object, passing an executor function:

Within the executor function, if the promise is fulfilled, we call the onFulfilled callback and resolve it with the promise's value. Similarly, if the promise is rejected, we call the onRejected callback and reject it with the promise's reason.

The next question is what to do with onFulfilled and onRejected callbacks if the promise is still in the pending state? We queue them to be called later, as follows:

We're done. Here's the second draft of our Promise class, including the then() method:

Here, we introduce two fields: onFulfilledCallbacks and onRejectedCallbacks as queues to hold callbacks. These queues are populated with callbacks via then() calls while the promise is pending, and they are called when the promise is fulfilled or rejected.

Go ahead test your Promise class:

It should output:

On the other hand, if you run the following test:

You would get:

Instead of:

Why? The issue lies in how the then() method processes callbacks when the YourPromise instance is already resolved or rejected at the time then() is called. Specifically, when the promise state is not pending, the then() method does not properly defer execution of the callback to the next micro-task queue. And that causes synchronous execution. In our example test:

⟶ The promise is immediately resolved with the value 'Immediately resolved'.

⟶ When promise.then() is called the state is already fulfilled, so the onFulfilled callback is executed directly without being deferred to the next micro-task queue.

Here the Rule 2.2.4 comes into play. This rule ensures that the then() callbacks (onFulfilled or onRejected) are executed asynchronously, even if the promise is already resolved or rejected. This means that the callbacks must not run until the current execution stack is completely clear and only platform code (like the event loop or micro-task queue) is running.

Why is this rule important?

This rule is one of the most important rules in the Promise/A specification. Because it ensures that:

⟶ Even if a promise is immediately resolved, its then() callback won't execute until the next tick of the event loop.

⟶ This behavior aligns with the behavior of the other asynchronous APIs in JavaScript such as setTimeout or process.nextTick.

How can we achieve this?

This can be achieved with either a macro-task mechanism such as setTimeout or setImmediate, or with a micro-task mechanism such as queueMicrotask or process.nextTick. Because the callbacks in a micro-task or macro-task or similar mechanism will be executed after the current JavaScript execution context finishes.

To fix the issue above, we need to ensure that even if the state is already fulfilled or rejected, the corresponding callbacks (onFulfilled or onRejected) are executed asynchronously using queueMicrotask. Here's the corrected implementation:

Run the previous example test code again. You should get the following output:

That's it.

By now, you should have a clear understanding of how callbacks from then() are deferred and executed in the next micro-task queue, enabling asynchronous behavior. A solid grasp of this concept is essential for writing effective asynchronous code in JavaScript.

What's next? Since this article didn't cover the full Promises/A specification, you can try implementing the rest to gain a deeper understanding.

Since you've made it this far, hopefully you enjoyed the reading! Please share the article.

Follow me on:
LinkedIn, Medium, and Github

The above is the detailed content of Create your own Promise in JavaScript. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

What should I do if I encounter garbled code printing for front-end thermal paper receipts? What should I do if I encounter garbled code printing for front-end thermal paper receipts? Apr 04, 2025 pm 02:42 PM

Frequently Asked Questions and Solutions for Front-end Thermal Paper Ticket Printing In Front-end Development, Ticket Printing is a common requirement. However, many developers are implementing...

Demystifying JavaScript: What It Does and Why It Matters Demystifying JavaScript: What It Does and Why It Matters Apr 09, 2025 am 12:07 AM

JavaScript is the cornerstone of modern web development, and its main functions include event-driven programming, dynamic content generation and asynchronous programming. 1) Event-driven programming allows web pages to change dynamically according to user operations. 2) Dynamic content generation allows page content to be adjusted according to conditions. 3) Asynchronous programming ensures that the user interface is not blocked. JavaScript is widely used in web interaction, single-page application and server-side development, greatly improving the flexibility of user experience and cross-platform development.

Who gets paid more Python or JavaScript? Who gets paid more Python or JavaScript? Apr 04, 2025 am 12:09 AM

There is no absolute salary for Python and JavaScript developers, depending on skills and industry needs. 1. Python may be paid more in data science and machine learning. 2. JavaScript has great demand in front-end and full-stack development, and its salary is also considerable. 3. Influencing factors include experience, geographical location, company size and specific skills.

Is JavaScript hard to learn? Is JavaScript hard to learn? Apr 03, 2025 am 12:20 AM

Learning JavaScript is not difficult, but it is challenging. 1) Understand basic concepts such as variables, data types, functions, etc. 2) Master asynchronous programming and implement it through event loops. 3) Use DOM operations and Promise to handle asynchronous requests. 4) Avoid common mistakes and use debugging techniques. 5) Optimize performance and follow best practices.

How to achieve parallax scrolling and element animation effects, like Shiseido's official website?
or:
How can we achieve the animation effect accompanied by page scrolling like Shiseido's official website? How to achieve parallax scrolling and element animation effects, like Shiseido's official website? or: How can we achieve the animation effect accompanied by page scrolling like Shiseido's official website? Apr 04, 2025 pm 05:36 PM

Discussion on the realization of parallax scrolling and element animation effects in this article will explore how to achieve similar to Shiseido official website (https://www.shiseido.co.jp/sb/wonderland/)...

The Evolution of JavaScript: Current Trends and Future Prospects The Evolution of JavaScript: Current Trends and Future Prospects Apr 10, 2025 am 09:33 AM

The latest trends in JavaScript include the rise of TypeScript, the popularity of modern frameworks and libraries, and the application of WebAssembly. Future prospects cover more powerful type systems, the development of server-side JavaScript, the expansion of artificial intelligence and machine learning, and the potential of IoT and edge computing.

How to merge array elements with the same ID into one object using JavaScript? How to merge array elements with the same ID into one object using JavaScript? Apr 04, 2025 pm 05:09 PM

How to merge array elements with the same ID into one object in JavaScript? When processing data, we often encounter the need to have the same ID...

Zustand asynchronous operation: How to ensure the latest state obtained by useStore? Zustand asynchronous operation: How to ensure the latest state obtained by useStore? Apr 04, 2025 pm 02:09 PM

Data update problems in zustand asynchronous operations. When using the zustand state management library, you often encounter the problem of data updates that cause asynchronous operations to be untimely. �...

See all articles