preface

“A thousand Hamlets for a thousand readers.”

This post is to share my promise in my mind (of course, I also combined with other excellent articles and specifications, and finally integrated my own ideas). It may be repeated sharing, but there will be new experiences.

Vernacular, from the simple to the deep, together uncover the principle behind promise.

Hopefully, you’ve already worked with Promise and used it to solve some asynchronous problems. This article does not do the use of instructions, based on the basis of everyone will use, so that fully understand the implementation behind the logic flow. After careful consumption and careful digestion. I trust you won’t be unfamiliar with Promise.

Then you’ll be more comfortable using them in a business situation, making promises during an interview, or even writing them out by hand.

Here are some links to help you understand:

[例 句] The Promise is A Promise

Ruan Yifeng promise object

— — — — — — — — — — — — — — — — — — — — —

Easy to understand the Promise principle and realize it (2)

👌 began

We start with the basic implementation, writing and understanding the full Promise step by step. For the sake of understanding, we will initially leave out errors and exceptions. After combing the whole process, it will be easier to add.

Function getData () {return new Promise((resolve) => {HTTP.$get(url, (result) = > {resolve (result)})})} getData (). Then ((data) = > {/ / business to deal with the console. The log (data)})Copy the code

As the above example, in daily business scenarios, we obtain data through asynchronous request, and basically we will use promise to unify the request code encapsulation. Register our own logic to operate in the business logic through the THEN method. Show some scenes and combine with handwritten code to give you a deeper understanding.

— — — — — — — — — — — — — — — — — — — — —

Start with basics (Version 1)

  • The then on the prototype collects callbacks
  • The collected callback can be executed uniformly after the call to resolve
Class Promise {// Callback collection callbacks = [] constructor (fn) {// Resolve execution scope is not necessarily current, Bind (this))} then (ondepressing) {this.callbacks. Push (ondepressing)} _resolve (val) {const ForEach (fn => {fn(val)})}} const p = new Promise((resolve) => {setTimeout(() => {setTimeout() => { resolve(11) }, 1000) }).then((data) => { console.log(data) }) p.then((data) => { console.log(data,'1') }) p.then((data) => { console.log(data,'2') })Copy the code

At this point, our Promise is ready to wrap asynchronous operations and handle the collected callbacks. You can also assign this promise to a variable, registering callbacks multiple times. Let’s clarify the current logic:

  • The Callbacks in the Promise are defined as arrays to collect callbacks, which may register more than one.
  • When a promise is instantiated, pass the resolve method to FN (the parameter method when the user instantiates a promise)
  • Promise aggregates the callback functions registered by the user through the then method
  • The previously collected callbacks are processed in sequence when the user executes the resolve method

But two problems have arisen

const p = new Promise((resolve) => {
  resolve(11)
})

p.then((data) => {
  console.log(data,'1')
}).then((data)=>{
  console.log(data,'2')
})
Copy the code

  1. Our Promise can currently only wrap asynchronous code, leaving the Callbacks array empty if resolve executes immediately before the then method has time to collect callbacks. The callback function cannot be executed.
  2. At this point, promises did not support chained calls

The A+ specification states that promises represent the result of an asynchronous operation, so our logic for executing resolve will have to be changed for now.

— — — — — — — — — — — — — — — — — — — — —

Added delay mechanism and chained calls (version 2)

  • Uniform asynchronous execution
  • Simple chain call
[] constructor (fn) {fn(this._resolve.bind(this))} then (ondepressing) {// This Promise will be fulfilled soon. this.callbacks.push(onFulfilled) return this } _resolve (val) { const me = this setTimeout(() => { me.callbacks.forEach(fn => { fn(val) }) }) } }Copy the code

  • Return the current this in the then method (supports chained calls)
  • Add setTimeout to resolve

After modification, it looks like there is something there. Chain calls and uniform asynchronous execution callbacks are complete. We’re actually only halfway there.

Think about it and you’ll find two new questions

// Const p = new Promise((resolve) => {resolve(1)}) console.log(data) }) }, If ((data) => {data += 1 return data}) then((data) => {console.log(data + 1)})Copy the code

  1. If resolve is executed, subsequent callbacks registered with THEN are never executed.
  2. Is the chain call really a chain call? (See figure below)

The A+ specification states that each promise’s then method must return A Promise instance (currently we return this). The chained calls were collected by the original Promise object, and all the callbacks returned the same value, which is not what we want, nor is it true for a chained call. What we need is a chained flow of data, and from there, this is one of the main points of this article.

In this case, we need to add states to manage our promises. This is also known as “pending”, “depressing” and “Rejected”, which stipulates that the state transformation in the promise is one-way and can only be changed from pending to “depressing” or “Rejected”. Then you need to modify the THEN function to return the Promise object to make it a more logical chain call.

— — — — — — — — — — — — — — — — — — — — —

State management, chain calls (version 3)

  • State management
  • Updated chain call (returns new Promise)
Class Promise {// Callback collection callbacks = [] // Return value save value = null // state management state = 'pending' constructor (fn) { Bind (this))} then (ondepressing = null) {const me = this // return promise ((resolve)) => {// Encapsulate the collected callback into the object me._handle({ondepressing, Resolve})})} _handle (cbObj) {if (this.state === 'pending') {this.callbacks. Push (cbObj) return} // If the callback function if (! This is a big pity) {this._resolve(this. Value) return} const val = cbobj. ondepressing (this. Value) // This is a big pity. Cbobj.resolve (val)} _resolve (val) {const me = this setTimeout(()=>{me.value = val me.state = 'fulfilled' me.callbacks.forEach(cbObj => { me._handle(cbObj) }) }) } }Copy the code

This version has a lot more code, but the logic is not complicated. Let’s go over the current logic again:

  • Callbacks registered by the user in the THEN method are simply wrapped in a new promise. We call it the built-in promise first, and the goal is to collect the next THEN callback into the built-in Promise, implementing a true chained data flow.
  • The handle method is added to handle the wrapped callback object. If the state is still pending, the collection is performed, and if the state is fulfilled, the resolve (uniformly perform the collection callback) is performed to obtain the return value. The return value is passed to resolve, which has the promise built in.
  • The state state is added, which is initially pending. After executing the resolve method, the state is changed to depressing. With state judgment, subsequent registered THEN callbacks can be performed even if a fulfilled promise has been fulfilled.
Const p = new Promise((resolve) => {resolve(1)}) setTimeout(() => {p.chen ((data) => {console.log(data)})},  1000) p.then((data) => { data += 1 return data }).then((data) => { console.log(data + 1) })Copy the code

At this point, the above application case is ready for implementation. We can already pipeline the results to the THEN callback, and when the processing is complete, to the next THEN callback. Maybe some of you don’t understand it very well, so let’s try to understand it in vernacular.

  • What we need is for each callback registered on then to process the result in turn and pass it on.
  • Then is the method in the promise. So then must return a promise object.
  • We’ll create a new promise in the then method and return it.
  • The key is, when does the built-in resolve method of the Promise execute? Of course, after the user’s current callback completes, we can change the state of the built-in Promise.
  • At this point, you can think of the built-in Promise as a new, initial instance that starts a new round of callback collection and execution.

Here comes a new problem

  1. What if the user registers the callback as an asynchronous function, or returns a Promise object?
Then ((data) => {// Execute again in a callback and return a new promise. Return new Promise((resolve) => { HTTP.$get(url + data.id, (result) => {resolve(result)})}). Then ((result) => {// Console.log (result)})Copy the code

What if the user registers a callback that returns a Promise object? All we need to do is register resolve with the built-in promise as a callback to the user promise. That’s the then appended to the user’s promise.

— — — — — — — — — — — — — — — — — — — — —

(Version 3.1)

Class Promise {// Callback collection callbacks = [] // Return value save value = null // state management state = 'pending' constructor (fn) { fn(this._resolve.bind(this)) } then (onFulfilled = null) { const me = this return new Promise((resolve) => { // Encapsulate the collected callback into the object me._handle({ondepressing, Resolve})})} _handle (cbObj) {if (this.state === 'pending') {this.callbacks. Push (cbObj) return} // If the callback function if (! cbObj.onFulfilled) { this._resolve(this.value) return } const val = cbObj.onFulfilled(this.value) cbObj.resolve(val) } _resolve (val) {const me = this Is there then method can mount the if (val && (typeof val = = = 'object' | | typeof val = = = 'function')) {const valThen = val. Then the if (valThen && typeof valThen === 'function') {// Keep this pointing correctly, if not bound, this in valThen will point to the built-in promise, Instead of the user's own promise valthen.call (val, me._resolve.bind(me)) return } } setTimeout(()=>{ me.value = val me.state = 'fulfilled' me.callbacks.forEach(cbObj => { me._handle(cbObj) }) }) } }Copy the code

  • We only added judgments to the resolve method, which handles cases where the result value could be a Promise instance or something like a Promise object.
  • Register the built-in Promise’s resolve as the user’s callback via a scoped binding.

After modification, it can handle the application example above. Now that you’ve mastered the whole logic of promise. We can then add error status and exception handling.

— — — — — — — — — — — — — — — — — — — — —

Improve status management and exception handling (version 4)

  • Add the Rejected and reject methods
  • Add exception handling
Class Promise {// callback collection callbacks = [] // return value save value = NULL // error cause Reason = null // state management state = 'pending' constructor (fn) {// Instantiate initially, Try {fn(this._resolve.bind(this), this._reject.bind(this)) } catch (error) { this._reject(error) } } then (onFulfilled = null, OnFulfilled = null) {const me = this return Promise((resolve, reject) => {// This return Promise((resolve, reject)) onRejected, resolve, Reject})})} _handle (cbObj) {if (this.state === 'pending') {this.callbacks. Push (cbObj) return} // Const cb = this. State === 'depressing'? cbObj.onFulfilled : cbObj.onRejected const stateF = this.state === 'fulfilled' ? Cbobj. resolve: cbobJ. reject // If (! cb) { stateF(this.state === 'fulfilled' ? This. Value: this. Reason) return} // Const val = cb(this. this.value : This. Reason) // Cbobj.resolve (val)} catch (error) {cbobj.reject (error)}} _resolve (val) {const me = this // Restrict state transition if (me.state! Return PROMISE if (me === val) {return me._reject(new TypeError(" Cannot return the same This is a big pity or on rejected callback."))} Is there then method can mount the if (val && (typeof val = = = 'object' | | typeof val = = = 'function')) {const valThen = val. Then the if (valThen && typeof valThen === 'function') {// Keep this pointing correctly, if not bound, this in valThen will point to the built-in promise, Instead of the user's own promise // resolve and reject also bind this valthen.call (val, me._resolve.bind(me), me._reject.bind(me)) return } } setTimeout(()=>{ me.value = val me.state = 'fulfilled' me._execute() }) } _reject (reason) {const me = this // restrict state conversion if (me.state! == 'pending') return setTimeout(()=>{ me.reason = reason me.state = 'rejected' me._execute() }) } _execute () { const me  = this me.callbacks.forEach(cbObj => { me._handle(cbObj) }) } }Copy the code

At first glance, the amount of code has increased considerably. The main logic is still the same, so let’s understand the logic again:

  • Exception catching was added to the initial instantiation
  • In the THEN method, error callbacks are also collected. Encapsulate both successful and error callbacks when wrapping callback objects.
  • In the Handle function, the corresponding user callback (success or failure) functions are obtained according to the promise state. The important thing here is whether the built-in functions are executed in resolve or reject. For ease of understanding, we use resolve to execute the result of the user callback function. The most complicated part of the Promise /A+ specification, however, is the logical judgment on this. In the specification, we need to consider more. Is the callback a promise that we define, or some other promise specification? If the result value is a Promise object, what’s the state like and so on
  • No new logic is added to resolve. It just adds a single change in state judgment, checks whether the return value is the current Promise object, and if it is, according to the specification, throws an error. Also, add error callback after. You need to pass reject processing in the then method of the user’s Promise instance.
  • Finally, the reject method and excute method are added, which is also easy to understand.

Finally, the promise was strong

If you look at this and it’s not clear, you can review it a few times. Until you get so familiar with it that you can write it by hand.

— — — — — — — — — — — — — — — — — — — — —

It’s not over

Version 4 code is still not enough to pass the Promise /A+ specification. There’s still a lot of detail to fill in. However, the details of the subsequent additions do not affect the main logic, and it is not difficult to understand the version 4 code before completing the specification.

The complete code in accordance with the Promise /A+ specification has been in my warehouse ⬇️, interested students can further study

GK11upup / myPromise​

We can test our promises through Promises -aplus-tests.

// Add the following code to your Promise file, Mypromise.deferred = function() {const defer = {} deferred. Promise = new MyPromise((resolve, resolve, defer)) reject) => { defer.resolve = resolve defer.reject = reject }) return defer } try { module.exports = MyPromise } catch (e) { }Copy the code

Follow and run in the terminal

npm install promises-aplus-tests -D
npx promises-aplus-tests promise.js
Copy the code

The promise prototype method and static method implementation will be added later.