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!

The differences in performance and efficiency between Python and JavaScript are mainly reflected in: 1) As an interpreted language, Python runs slowly but has high development efficiency and is suitable for rapid prototype development; 2) JavaScript is limited to single thread in the browser, but multi-threading and asynchronous I/O can be used to improve performance in Node.js, and both have advantages in actual projects.

JavaScript originated in 1995 and was created by Brandon Ike, and realized the language into C. 1.C language provides high performance and system-level programming capabilities for JavaScript. 2. JavaScript's memory management and performance optimization rely on C language. 3. The cross-platform feature of C language helps JavaScript run efficiently on different operating systems.

JavaScript runs in browsers and Node.js environments and relies on the JavaScript engine to parse and execute code. 1) Generate abstract syntax tree (AST) in the parsing stage; 2) convert AST into bytecode or machine code in the compilation stage; 3) execute the compiled code in the execution stage.

The future trends of Python and JavaScript include: 1. Python will consolidate its position in the fields of scientific computing and AI, 2. JavaScript will promote the development of web technology, 3. Cross-platform development will become a hot topic, and 4. Performance optimization will be the focus. Both will continue to expand application scenarios in their respective fields and make more breakthroughs in performance.

Both Python and JavaScript's choices in development environments are important. 1) Python's development environment includes PyCharm, JupyterNotebook and Anaconda, which are suitable for data science and rapid prototyping. 2) The development environment of JavaScript includes Node.js, VSCode and Webpack, which are suitable for front-end and back-end development. Choosing the right tools according to project needs can improve development efficiency and project success rate.

Yes, the engine core of JavaScript is written in C. 1) The C language provides efficient performance and underlying control, which is suitable for the development of JavaScript engine. 2) Taking the V8 engine as an example, its core is written in C, combining the efficiency and object-oriented characteristics of C. 3) The working principle of the JavaScript engine includes parsing, compiling and execution, and the C language plays a key role in these processes.

JavaScript is at the heart of modern websites because it enhances the interactivity and dynamicity of web pages. 1) It allows to change content without refreshing the page, 2) manipulate web pages through DOMAPI, 3) support complex interactive effects such as animation and drag-and-drop, 4) optimize performance and best practices to improve user experience.

C and JavaScript achieve interoperability through WebAssembly. 1) C code is compiled into WebAssembly module and introduced into JavaScript environment to enhance computing power. 2) In game development, C handles physics engines and graphics rendering, and JavaScript is responsible for game logic and user interface.


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

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

Hot Article

Hot Tools

DVWA
Damn Vulnerable Web App (DVWA) is a PHP/MySQL web application that is very vulnerable. Its main goals are to be an aid for security professionals to test their skills and tools in a legal environment, to help web developers better understand the process of securing web applications, and to help teachers/students teach/learn in a classroom environment Web application security. The goal of DVWA is to practice some of the most common web vulnerabilities through a simple and straightforward interface, with varying degrees of difficulty. Please note that this software

VSCode Windows 64-bit Download
A free and powerful IDE editor launched by Microsoft

SublimeText3 Chinese version
Chinese version, very easy to use

SecLists
SecLists is the ultimate security tester's companion. It is a collection of various types of lists that are frequently used during security assessments, all in one place. SecLists helps make security testing more efficient and productive by conveniently providing all the lists a security tester might need. List types include usernames, passwords, URLs, fuzzing payloads, sensitive data patterns, web shells, and more. The tester can simply pull this repository onto a new test machine and he will have access to every type of list he needs.

mPDF
mPDF is a PHP library that can generate PDF files from UTF-8 encoded HTML. The original author, Ian Back, wrote mPDF to output PDF files "on the fly" from his website and handle different languages. It is slower than original scripts like HTML2FPDF and produces larger files when using Unicode fonts, but supports CSS styles etc. and has a lot of enhancements. Supports almost all languages, including RTL (Arabic and Hebrew) and CJK (Chinese, Japanese and Korean). Supports nested block-level elements (such as P, DIV),
