Writing in the front

Yes, this is another article about handwritten promises. I think you’ve seen a lot of articles about promises. I won’t go into details about why some promises appear. Using typescript, we start with the ES6 Promise type definition, analyze the incoming parameters and return values of promises and related methods, and write a typescript version of a Promise.

Promise/A + specification

The Promise/A+ specification is the universal Promise library in the industry, and Promises need to be written in compliance with this specification.

Due to the excessive content, the following code will be implemented one by one, here are a few terms:

  • Fulfill (fulfill): refers to the sequence of actions, such as a state change or the execution of a callback, that occur when a promise succeeds. Although the specification is usedfulfillTo indicate the settlement, but in the later promise to achieve moreresolveTo refer to.
  • Reject: A sequence of actions that occurs when a promise fails.
  • Eventual Value: The so-called final value refers to the value transferred to the solution callback when the PROMISE is fulfill (FULFILL). Because the promise has the characteristics of one-off, when this value is delivered, it marks the end of the promise waiting state, so it is called the final value, sometimes referred to as value.
  • Reason: The rejection reason, which is the value passed to the rejection callback when a promise is rejected.

It’s worth noting that the core Promises/A+ specification doesn’t deal with how to create, resolve, and reject Promises. Instead, it focuses on providing A universal then method. So, by interacting with THE THEN method, Promises/A+ specifications are almost complete.

Realize the Promise

The basic function

First we implement the basic Promise functions, passing in the generator, reslove and reject, and calling the then function to get the basic value. Let’s look at the type definition of the constructor:

// This is the class itself, with all, race and other methods defined in it
interface PromiseConstructor {
    /** * A reference to the prototype. */
    readonly prototype: Promise<any>;
    /**
     * Creates a new Promise.
     * @param executor A callback used to initialize the promise. This callback is passed two arguments:
     * a resolve callback used to resolve the promise with a value or the result of another promise,
     * and a reject callback used to reject the promise with a provided reason or error.
     */
  	// This is the important part
    new <T>(executor: (resolve: (value? : T | PromiseLike<T>) =>void, reject: (reason? :any) = >void) = > void) :Promise<T>;
		// ...
}
Copy the code

The two remaining interface types are Promise

and PromiseLike

, where Promise

is the associated property interface of the instance object:


// At this point, the following is only the interface properties of ES2015. Since Promise has been updated, more instance properties will be explained later
interface Promise<T> {
    /**
     * Attaches callbacks for the resolution and/or rejection of the Promise.
     * @param onfulfilled The callback to execute when the Promise is resolved.
     * @param onrejected The callback to execute when the Promise is rejected.
     * @returns A Promise for the completion of which ever callback is executed.
     */
    then<TResult1 = T, TResult2 = never>(onfulfilled? : ((value: T) = > TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected? : ((reason: any) = > TResult2 | PromiseLike<TResult2>) | undefined | null) :Promise<TResult1 | TResult2>;
    / /...
}
Copy the code

The interface of the PromiseLike

is as follows:

interface PromiseLike<T> {
    /**
     * Attaches callbacks for the resolution and/or rejection of the Promise.
     * @param onfulfilled The callback to execute when the Promise is resolved.
     * @param onrejected The callback to execute when the Promise is rejected.
     * @returns A Promise for the completion of which ever callback is executed.
     */
    then<TResult1 = T, TResult2 = never>(onfulfilled? : ((value: T) = > TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected? : ((reason: any) = > TResult2 | PromiseLike<TResult2>) | undefined | null): PromiseLike<TResult1 | TResult2>;
}
Copy the code

As you can see, a PromiseLike interface defines an object with a THEN () method (officially called Thenable), which is treated as a Promise instance whenever it has a THEN () method.

// Try it out
new Promise((resolve) = > {
  resolve({
    prop: 'common property'.// We construct a then method ourselves, and Promise is automatically reslove and reject for the then method
    then(reslove2: any) {
      reslove2('promiselike')
    }
  })
}).then((res) = > {
  // Sure enough, it was regarded as a Promise
  console.log(res) // promiselike
})
Copy the code

It should be noted that the asynchronous execution mechanism of the callback function inside Promise uses microtasks, while the environment we use does not provide us with API related to microtasks. Therefore, setTimeout is used for asynchronous simulation in the code, and the callback is directly pushed to the end of the event loop.

If you’re not familiar with event loops and microtasks, check out this article to thoroughly understand JS event polling.

Here is the code implementation:

// Create a variable with an enumeration type to hold the response status
enum Status {
  PENDING = 'pending',
  FULFILLED = 'fulfilled',
  REJECTED = 'rejected'
}

// Put the required type out
type Resolve<T> = (value: T | PromiseLike<T>) = > void
type Reject = (reason? :any) = > void
type Executor<T> = (resolve: Resolve<T>, reject: Reject) = > void
type onFulfilled<T, TResult1> =
  | ((value: T) = > TResult1 | PromiseLike<TResult1>)
  | undefined
  | null
type onRejected<TResult2> =
  | ((reason: any) = > TResult2 | PromiseLike<TResult2>)
  | undefined
  | null


/* Typeof value === 'object' && value! = = null) | | typeof value = = = 'function') && typeof (value as PromiseLike < T >). Then = = = 'function' to determine, There are also better typescript prompts */
function isPromise(value: any) :value is PromiseLike<any> {
  return(((typeof value === 'object'&& value ! = =null) | |typeof value === 'function') &&
    typeof value.then === 'function')}class MyPromise<T> {
  // The initial state
  status: Status = Status.PENDING
  // Save the final value of the current Promise, which must have a value
  privatevalue! : T// Save the grounds for the current Promise
  privatereason? :any
  private onFulfilledCallback: (() = > void=) [] []// Successful callback
  private onRejectedCallback: (() = > void=) [] []// Failed callback

  constructor(executor: Executor<T>) {
    try {
      // Prevent this from being lost
      executor(this._resolve.bind(this), this._reject.bind(this))}catch (e) {
      // Error direct reject
      this._reject(e)
    }
  }
  private _resolve(value: T | PromiseLike<T>) {
    try{
      // Simulate microtask asynchrony
      setTimeout(() = > {
        // Determine if it is a Thenable object. If it is, fetch the value immediately after pending
        if (isPromise(value)) {
          // Pass the internal resolve and reject functions again
          value.then(this._resolve.bind(this), this._reject.bind(this))
          return
        }

        // If the state is pending, it will become depressing
        if (this.status === Status.PENDING) {
          this.status = Status.FULFILLED
          // The value type will only be T
          this.value = value
          // The callback passed when.then is executed after resolve
          this.onFulfilledCallback.forEach((fn) = > fn())
        }
      })
    }catch(err){
      // Catch after an internal error is thrown when a Promise is passed in
      this._reject(err)
    }
  }

  // The internal reject function, which we pass in to the user to call with our instance Promise
  private _reject(reason: any) {
    // Same as above, no value penetration is required here, so there is no need to check whether it is a Promise object
    setTimeout(() = > {
      if (this.status === Status.PENDING) {
        this.status = Status.REJECTED
        this.reason = reason
        this.onRejectedCallback.forEach((fn) = > fn())
      }
    })
  }

  public then<TResult1 = T, TResult2 = never>( onfulfilled? : onFulfilled<T, TResult1>, onrejected? : onRejected<TResult2> ): MyPromise<TResult1 | TResult2> {// About ondepressing and onRejected if there is no upload, we need to carry out the transparent transmission of the value, but in the implementation of the basic function we will ignore this problem, and the function will be passed by default
    // Determine whether the current status is pending, if asynchronous reslove or reject
    if (this.status === Status.FULFILLED) {
      setTimeout(() = >{ onfulfilled! (this.value)
      })
    }
    if (this.status === Status.REJECTED) {
      setTimeout(() = >{ onrejected! (this.reason)
      })
    }
    if (this.status === Status.PENDING) {
      // If it is pending, you need to store the onFulfilled and onRejected functions, and execute them successively after the status is determined
      // There is a setTimeout for the callback
      this.onFulfilledCallback.push(() = >{ onfulfilled! (this.value)
      })
      this.onRejectedCallback.push(() = >{ onrejected! (this.reason)
      })
    }
    Then returns a Promise value that depends on the state and result of the previous Promise
    return new MyPromise(() = >{})}}Copy the code

OK, the above has completed a Promise with only one chain, so let’s test it:

/ / synchronize
new MyPromise((reslove, reject) = > {
  reslove('success')
}).then(
  (res) = > {
    console.log(res) // success
  },
  (err) = > {
    console.log(err)
  }
)

/ / asynchronous
new MyPromise((reslove, reject) = > {
  setTimeout(() = > {
    reslove('timeout success')},2000)
}).then(
  (res) = > {
    console.log(res) // timeout success
  },
  (err) = > {
    console.log(err)
  }
)
Copy the code

The result is to print SUCCESS immediately and timeout Success two seconds later, which is in line with our expectations.

Depth of THEN (emphasis)

As mentioned earlier, the entire core of the Promise/A+ specification is the handling of then methods. In addition, there is a third-party library called Promises – Aplus-Tests to test whether the promises we wrote meet the specification. We will also use this library for future tests.

Chain calls

To implement the chain call of THEN, you need to return a new Promise. Meanwhile, whatever values are returned by the onfulfilled and onRejected callback functions in then can be obtained in the callback function parameters of the new Promise’s THEN method.

We use X as the onfulfilled or onrejected return value passed in the THEN method, and use promise to represent the new promise returned by the THEN method. According to the Promise/A+ specification, We should do the following for this [[Resolve]](promise, x) :

  • xpromiseequalIf:promisexTo point to the same objectTypeErrorRefuse to perform on grounds of evidencepromise.
  • xFor the PromiseIf:xFor Promise, makepromiseacceptxIn the state.
    • ifxIn the waiting state,promiseMust remain in wait state untilxTo be carried out or rejected
    • ifxIn the execution state, execute with the same valuepromise
    • ifxIn the rejection state, reject with the same groundspromise
  • xIs an object or function
    • ifxFor an object or function:
      1. thex.thenAssigned tothen
      2. If you takex.thenThrows an error when the value ofe, theeRefusal on grounds of proofpromise
      3. ifthenTheta is a function of thetaxAs the scope of the functionthisCall it. Pass two callback functions as arguments, the first of which is calledresolvePromiseThe second parameter is calledrejectPromise
        1. ifresolvePromiseIn order to valueyRun when called for parameters[[Resolve]](promise, y)(This is the recursive part of the solution.)
        2. ifrejectPromiseTo according to therIs called with an argument, then with an argumentrRefused topromise
        3. ifresolvePromiserejectPromiseAre called, or are called more than once by the same parameter, the first call takes precedence and the rest are ignored
        4. If the callthenMethod throws an exceptione:
          • ifresolvePromiserejectPromiseAlready called, ignore it
          • Or otherwiseeRefusal on grounds of proofpromise
      4. ifthenIt’s not a functionxExecute for parameterspromise
  • ifxIs not an object or a functionxExecute for parameterspromise

General flow chart:

While that may seem like a lot, the general idea is to get us to follow the following rules:

  • If the callback function in then returns a value (not a Promise instance or thenable object) or no value (i.e., undefined), then the Promise returned by then becomes the accepted state, And the returned value is used as the parameter value of the callback function (ondepressing) that accepts the state.

    new Promise<void> ((reslove) = > {
      reslove()
    })
      .then(() = > {
        return 'success'
      })
      .then((res) = > {
        console.log(res) // success
      })
    Copy the code
  • If the callback function in THEN throws an error, the Promise returned by then becomes the rejected state, and the error is used as an argument to the onRejected state callback function.

    new Promise<void> ((reslove) = > {
      reslove()
    })
      .then(() = > {
        throw new Error('error message')
      })
      .then(
        () = > {},
        (err) = > {
          console.log(err) // Error: error message})Copy the code

    The loop returns a Promise and throws an error like this:

    const promise2: Promise<any> = new Promise<void> ((reslove) = > {
      reslove()
    }).then(() = > {
      return promise2
    })
    
    promise2.then(() = > {}, console.log) // [TypeError: Chaining cycle detected for promise #<Promise>]
    Copy the code
  • This is a big pity. If the callback function in THEN returns a Promise that is the acceptance state (which is a pity), the Promise returned by THEN will also become the acceptance state, and the parameter values of the callback function in THEN will be gradually separated. As the parameter value of the accepted state callback function for the returned Promise.

    new Promise<void> ((reslove) = > {
      reslove()
    })
      .then(() = > {
        return Promise.reslove('success')
      })
      .then((res) = > {
        console.log(res) // success
      })
    Copy the code
  • If the callback function of THEN returns a Promise in the rejected state (promise2), then the Promise returned by THEN becomes the rejected state, and the parameter value of the then callback function in the rejected state is changed to the promise2 state. As the parameter value of the rejection state callback function for the returned Promise.

    new Promise<void> ((reslove) = > {
      reslove()
    })
      .then(() = > {
    		return new Promise.reject('error message')
      })
      .then(
        () = > {},
        (err) = > {
          console.log(err) // error message})Copy the code
  • If the callback function in THEN returns a Promise that is pending (let’s call it promise3 for now), then returns a Promise whose state is also pending and whose final state is the same as the final state of promise3. And the parameters of the callback function that calls THEN when it enters the final state are the same as those of the callback function that calls THEN when it enters the final state of promise3.

    new Promise<void> ((reslove) = > {
      reslove()
    })
      .then(() = > {
    		return new Promise((reslove, reject) = > {
          	setTimeout(() = >{
    					reslove('delay')},2000)
        })
      })
      .then(
       	res= > {
          console.log(res) // Print delay after two seconds})Copy the code

Ok, following the above procedure, let’s write the handler function:

function resolvePromise<T> (promise2: MyPromise
       
        , x: T | PromiseLike
        
         , resolve: Resolve
         
          , reject: Reject
         
        
       ) {
  // Do not refer to the same object, otherwise it will loop indefinitely
  if (promise2 === x) {
    const e = new TypeError(
      'TypeError: Chaining cycle detected for promise #<MyPromise>'
    )
    // Clear the stack. I'm not sure why Promise cleared this
    e.stack = ' '
    // Go straight to the wrong callback
    return reject(e)
  }
  let called = false // Prevent multiple calls

  // If x is a Promise, we know from the above that it is a Promise or, like Promise, that an object has a then method. We can see that the same judgment applies to whether it is an object or a function, so we can omit it

  // If x is an object or function
  if ((typeof x === 'object'&& x ! =null) | |typeof x === 'function') {
    try {
      /* Stores a reference to x.teng, and then tests and calls that reference to avoid multiple access to the x.teng property. This precaution ensures that the property is consistent because its value can be changed during retrieval calls. Note: We can use our encapsulated isPromise method here, but since we're following the process, let's do it
      // Change the type manually
      const then = (x as PromiseLike<T>).then
      if (typeof then === 'function') {
        X.teng (()=>{},()=>{})
        then.call(
          x,
          (y) = > {
            if (called) return
            called = true
            // If it is a Promise, we should recursively get the value of the final state, pass in the same handler, and throw success or failure directly to the outermost layer
            resolvePromise(promise2, y, resolve, reject)
          },
          (r) = > {
            if (called) return
            called = true
            // If an incoming Promise is rejected, it is thrown directly to the outermost layer
            reject(r)
          }
        )
      } else {
        // This is not a Promise object and is treated as a normal value
        resolve(x)
      }
    } catch (e) {
      // If there is an error. It goes straight into the reject state
      // However, if the state has changed before the error occurs, it does not matter for a long time
      if (called) return
      called = true
      reject(e)
    }
  } else {
    // Common value processing
    resolve(x)
  }
}
Copy the code

Can be the above process of code implementation and description step by step into view, basically can be consistent.

Put it into the then:

class MyPromise<T> {
  // ...
  public then<TResult1 = T, TResult2 = never>( onfulfilled? : onFulfilled<T, TResult1>, onrejected? : onRejected<TResult2> ): MyPromise<TResult1 | TResult2> {// Now we associate this newly generated Promise with the current Promise
    const promise2 = new MyPromise<TResult1 | TResult2>((resolve, reject) = > {
      if (this.status === Status.FULFILLED) {
        setTimeout(() = > {
          try {
            // Get x, then make a connection with the Promise to return
            letx = onfulfilled! (this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }
      if (this.status === Status.REJECTED) {
        setTimeout(() = > {
          try {
            // Get x, then make a connection with the Promise to return
            letx = onrejected! (this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }
      if (this.status === Status.PENDING) {
        // If it is pending, you need to store the onFulfilled and onRejected functions, and execute them successively after the status is determined
        // There is a setTimeout for the callback
        this.onFulfilledCallback.push(() = > {
          try {
            letx = onfulfilled! (this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
        this.onRejectedCallback.push(() = > {
          try {
            letx = onrejected! (this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }
    })
    return promise2
  }

  / /...
}
Copy the code

Test it out:

new MyPromise<void> ((resolve) = > {
  resolve()
})
  .then(() = > {
    return 'step1'
  })
  .then((res) = > {
    return res + ':' + 'step2'
  })
  .then((res) = > {
    console.log(res) // step1:step2
  })
Copy the code

Good. Perfect as expected.

The value of the penetration

We used then under the assumption that we would always pass callbacks to THEN, but in fact both callbacks can be defaulted in the Promise/A+ specification, which is why I added them? . When we do not pass a callback function to it, this is when the value’s penetration effect is triggered.

// Like the following
new Promise((reslove) = > {
  reslove('hello')
})
  .then()
  .then()
  .then()
  .then((res) = > {
    console.log(res) // 'hello'
  })
Copy the code

So we need to transform our then function, and the transformation method is actually very simple:

class MyPromise<T> {
  // ...
  public then<TResult1 = T, TResult2 = never>( onfulfilled? : onFulfilled<T, TResult1>, onrejected? : onRejected<TResult2> ): MyPromise<TResult1 | TResult2> {// If it is not a function, the value is passed through. A successful callback returns the same value, and a failed callback simply throws an error
    // Note that you cannot add a default value to the argument passed above, because you need to determine if it is a function
    const onfulfilledFn =
      typeof onfulfilled === 'function'
        ? onfulfilled
        : (v: T | TResult1) = > v as TResult1
    const onrejectedFn =
      typeof onrejected === 'function'
        ? onrejected
        : (e: any) = > {
            throw e
          }
	  Onfulfilled FN (onrejected) = onrejectedFn (onrejectedFn); // This is a big pity
    // Now we associate this newly generated Promise with the current Promise
    const promise2 = new MyPromise<TResult1 | TResult2>((resolve, reject) = > {
      if (this.status === Status.FULFILLED) {
        setTimeout(() = > {
          try {
            // Get x, then make a connection with the Promise to return
            let x = onfulfilledFn(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }
      if (this.status === Status.REJECTED) {
        setTimeout(() = > {
          try {
            // Get x, then make a connection with the Promise to return
            let x = onrejectedFn(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }
      if (this.status === Status.PENDING) {
        // If it is pending, you need to store the onFulfilled and onRejected functions, and execute them successively after the status is determined
        // There is a setTimeout for the callback
        this.onFulfilledCallback.push(() = > {
          try {
            let x = onfulfilledFn(this.value)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
        this.onRejectedCallback.push(() = > {
          try {
            let x = onrejectedFn(this.reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (e) {
            reject(e)
          }
        })
      }
    })

    return promise2
  }

  / /...
}
Copy the code

There you go. Congratulations, you’ve completed the most important part of Promise, and the rest of the API is basically built around that part.

Specification test

Use the third-party library we mentioned earlier to test the Promise specification:

npm install promises-aplus-tests -D
# or
yarn add promises-aplus-tests -D
Copy the code
// Add to the end of the file

// Ignore typescript validation
// @ts-ignore
MyPromise.defer = MyPromise.deferred = function () {
  let dfd: any = {}
  dfd.promise = new MyPromise((resolve, reject) = > {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}

export = MyPromise
Copy the code

Then use TSC to compile the ts file and run NPX promises-aplus-tests to compile the js file location.

Unfortunately, our Promise didn’t pass all of the tests, but we can see that all 16 failed tests reported the same error, calling the callback with a timeout (over 200 ms delay), My personal understanding is that the setTimeout API does not fully simulate microtasks and causes delay effects.

If we look at the test results alone and want to pass all the tests, we can make a change to the reslove function inside the original Promise definition:

class MyPromise{
  private _resolve(value: T | PromiseLike<T>) {
    try{
      setTimeout(() = > {
        If (isPromise(value)) {value.then(this._resolve.bind(this), this._reject. Bind (this)) return} */

        if (this.status === Status.PENDING) {
          this.status = Status.FULFILLED
          this.value = value as T // Cast type
          this.onFulfilledCallback.forEach((fn) = > fn())
        }
      })
    }catch(err){
      this._reject(err)
    }
  }
}
Copy the code

Of course, although we pass all the tests, it is obvious that the results are not as expected. When we use a PromiseLike object, the results are not the same as the real Promise:

// Use the original example
new Promise((resolve) = > {
  resolve({
    prop: 'common property'.then(reslove2: any) {
      reslove2('promiselike')
    }
  })
}).then((res) = > {
  // Real promises are promiselike
  console.log(res) // { prop: 'common property', then: [Function: then] } 
})
Copy the code

So, as far as the results are concerned, it’s basically qualified.

Promise’s extension method

Now that we’ve completed the core of the Promise, we can rely on the previous code to refine our Promise again.

Note: In the Promise interface definition, all instance methods are defined in the Promise

interface, and all static methods are defined in the PromiseConstructor interface.

Promise.prototype.catch

The promise.prototype. Catch (onRejected) method returns a Promise and handles the rejected case. It behaves the same as calling promise.prototype. then(undefined, onRejected).

Interface type:

interface Promise<T> {
    /**
     * Attaches a callback for only the rejection of the Promise.
     * @param onrejected The callback to execute when the Promise is rejected.
     * @returns A Promise for the completion of the callback.
     */
    catch<TResult = never>(onrejected? : ((reason: any) = > TResult | PromiseLike<TResult>) | undefined | null) :Promise<T | TResult>;
}
Copy the code

Implementation code:

interface MyPromise {
  // This is actually how it is called internally
	 public catch<TResult = never>( onrejected? : onRejected<TResult> ): MyPromise<T | TResult> {return this.then(null, onrejected)
  }
}
Copy the code

Promise.resolve

The promise.resolve (value) method returns a Promise object resolved with the given value.

  • If the value is a Promise instance, the Promise instance is returned.
  • If this value is zerothenable, will always followthenableGet the final state of it.
  • If none of these values are present, the returned Promise instance will use this value as the final value of the successful state.

Interface type:

interface PromiseConstructor {
	  // We can see that there are two types of function bodies, so we need to define function overloading
     /**
     * Creates a new resolved promise.
     * @returns A resolved promise.
     */
    resolve(): Promise<void>;

    /**
     * Creates a new resolved promise for the provided value.
     * @param value A promise.
     * @returns A promise whose internal state matches the provided promise.
     */
    resolve<T>(value: T | PromiseLike<T>): Promise<T>;
}
Copy the code

Implementation code:

interface MyPromise {
  // Function overload
  static resolve(): MyPromise<void>
  static resolve<T>(value: T | PromiseLike<T>): MyPromise<T>
  // The last function entity needs to support both of the above function-overloaded types, so we make it optional
  staticresolve<T>(value? : T | PromiseLike<T>): MyPromise<T> {// If it is a Promise, return the current Promise directly
    if (value instanceof MyPromise) {
      return value
    }
    return new MyPromise((resolve) = > {
      // We have already done thenable internally, so we will reslove directly
      // The value must be passed, so the inference is forced hereresolve(value!) }}})Copy the code

Promise.reject

The promise.reject (reason) method returns a Promise object with a reason for the rejection.

Interface type:

interface PromiseConstructor {
    /**
     * Creates a new rejected promise for the provided reason.
     * @param reason The reason the promise was rejected.
     * @returns A new rejected Promise.
     */
    reject<T = never>(reason? :any) :Promise<T>;
}
Copy the code

Implementation code:

interface MyPromise {
  static reject<T = never>(reason? :any): MyPromise<T> {
 		// No additional judgment is required
    return new MyPromise((resolve, reject) = > {
      reject(reason)
    })
  }
}
Copy the code

Promise.all

Promise.all(iterable) takes an iterable object and returns a Promise instance, This example will be fulfilled with all thenable states in the iterable parameter or with a fulfilled state if thenable is not included in the parameter, and reslove will be an array containing all reslove values passed in for Thenable. If the thenable parameter has a rejected state, the instance is also in rejected state, and rejects the result of the first thenable failure. If the status of thEnable is pending, the status of this instance is also pending.

Interface definition:

interface PromiseConstructor {
      /**
     * Creates a Promise that is resolved with an array of results when all of the provided Promises
     * resolve, or rejected when any Promise is rejected.
     * @param values An array of Promises.
     * @returns A new Promise.
     */
    all<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>, T10 | PromiseLike<T10>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>;
  
    all<T1, T2, T3, T4, T5, T6, T7, T8, T9>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>;
  
    all<T1, T2, T3, T4, T5, T6, T7, T8>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8]>;
  
    all<T1, T2, T3, T4, T5, T6, T7>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>]): Promise<[T1, T2, T3, T4, T5, T6, T7]>;
  
    all<T1, T2, T3, T4, T5, T6>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>]): Promise<[T1, T2, T3, T4, T5, T6]>;
  
    all<T1, T2, T3, T4, T5>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>]): Promise<[T1, T2, T3, T4, T5]>;

    all<T1, T2, T3, T4>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>]): Promise<[T1, T2, T3, T4]>;
  
    all<T1, T2, T3>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>]): Promise<[T1, T2, T3]>;
  
    all<T1, T2>(values: readonly [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>]): Promise<[T1, T2]>;

    all<T>(values: readonly (T | PromiseLike<T>)[]): Promise<T[]>;
    // The incoming Promise
      
        may have a different T and override a different tuple type
      
    
    // see: lib.es2015.iterable.d.ts
    all<T>(values: Iterable<T | PromiseLike<T>>): Promise<T[]>;
}
Copy the code

As we can see from the type definition, when we pass promise.all () an Iterable, the parameters are also correct, and the array is essentially an iterator, so our coding can be completely expanded around the iterator.

Implementation code:

interface MyPromise {
  static all<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(
  values: readonly [
   T1 | PromiseLike<T1>,
   T2 | PromiseLike<T2>,
   T3 | PromiseLike<T3>,
   T4 | PromiseLike<T4>,
   T5 | PromiseLike<T5>,
   T6 | PromiseLike<T6>,
   T7 | PromiseLike<T7>,
   T8 | PromiseLike<T8>,
   T9 | PromiseLike<T9>,
   T10 | PromiseLike<T10>
   ]
  ): MyPromise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>
  / /... It takes too much space
  static all<T>(values: Iterable<T | PromiseLike<T>>): MyPromise<T[]>
  // The actual implementation here is exactly the same as the iterator
  static all<T>(values: Iterable<T | PromiseLike<T>>): MyPromise<T[]> {
    return new MyPromise((resolve, reject) = > {
      // PromiseLike
      
        objects are traced to T
      
      const resultArr: T[] = []
      // Determine if all is completed
      const doneArr: boolean[] = []
      // Get the iterator object
      let iter = values[Symbol.iterator]()
      {value: XXX, done: false}
      let cur = iter.next()
      // Determine if the iterator is finished iterating and place the last value in the result array
      const resolveResult = (value: T, index: number, done? :boolean) = > {
        resultArr[index] = value
        doneArr[index] = true
        if (done && doneArr.every((item) = > item)) {
          resolve(resultArr)
        }
      }
      for (let i = 0; ! cur.done; i++) {const value = cur.value
        doneArr.push(false)
        cur = iter.next()
        if (isPromise(value)) {
          value.then((value: T) = > {
            resolveResult(value, i, cur.done)
          }, reject)
        } else {
          resolveResult(value, i, cur.done)
        }
      }
    })
  }
Copy the code

Promise.race

The promise.race (iterable) method takes an iterable object and returns a Promise. Once a thenable status in the iterator changes to fulfiled or Rejected, The instance’s state becomes Fulfiled or Rejected.

Interface definition:

interface PromiseConstructor {
    /**
     * Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
     * or rejected.
     * @param values An array of Promises.
     * @returns A new Promise.
     */
    race<T>(values: readonly T[]): Promise<T extends PromiseLike<infer U> ? U : T>;

    // see: lib.es2015.iterable.d.ts
    race<T>(values: Iterable<T>): Promise<T extends PromiseLike<infer U> ? U : T>;
}
Copy the code

Code implementation:

class MyPromise {
  static race<T>(
    values: Iterable<T>
  ): MyPromise<T extends PromiseLike<infer U> ? U : T>
  static race<T>(
    values: readonly T[]
  ): MyPromise<T extends PromiseLike<infer U> ? U : T>
  // Use the iterator directly
  static race<T>(
    values: Iterable<T>
  ): MyPromise<T extends PromiseLike<infer U> ? U : T> {
      return new MyPromise((resolve, reject) = > {
        const iter = values[Symbol.iterator]()
        let cur = iter.next()
        while(! cur.done) {const value = cur.value
          cur = iter.next()
          if (isPromise(value)) {
            value.then(resolve, reject)
          } else {
            // The normal value is T, but Typescript can't go any further and needs to convert it manually
            resolve(value as T extends PromiseLike<infer U> ? U : T)
          }
        }
      })
  }
}
Copy the code

Promise.prototype.finally

ES2018 proposed

Promise. Prototype. Finally onfinally () method returns a new Promise, and the state of Promise for Promise chain top state of a Promise. At the end of the last Promise, the specified callback function will be executed regardless of the result state, which is fulfilled or Rejected. This provides a way for code to be executed after a Promise is successfully completed or not. Avoid the need to write the same statement once in both then() and catch().

Specific usage:

// Finally is not used
new Promise((resolve) = > {
  	resolve()
}).then(() = > {
  console.log('success')
  console.log('finally')
})
.catch(() = > {
  console.log('error')
  console.log('finally')})/ / to use finally
new Promise((resolve) = > {
  	resolve()
}).then(() = > {
  console.log('success')
})
.catch(() = > {
  console.log('error')
})
.finally(() = > {
    console.log('finally')})Copy the code

Interface definition:

interface Promise<T> {
      /**
     * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The
     * resolved value cannot be modified from the callback.
     * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).
     * @returns A Promise for the completion of the callback.
     */
    finally(onfinally? : (() = > void) | undefined | null) :Promise<T>
}
Copy the code

Code implementation:

class MyPromise {
  // Will it be executed anyway
  public finally(onfinally? : onFinally): MyPromise<T> {return this.then(
      (value) = >
        MyPromise.resolve(
			  // If onFinally returns a thenable, it will wait for the thenable state to change before making subsequent promises
          typeof onfinally === 'function' ? onfinally() : onfinally
        ).then(() = > value),
      (reason) = >
        MyPromise.resolve(
          typeof onfinally === 'function' ? onfinally() : onfinally
        ).then(() = > {
          throw reason
        })
    )
  }
}
Copy the code

Promise.allSettled

ES2020 proposed

Promise.allSettled(iterable)Method receives a可迭代Object that returns an instance of Promise,The state of the instance is alwaysfulfilledorpending. in可迭代Within the parameters of theAll of thethenableWhether the state isfullfilledorrejected.resloveorrejectAll values are wrapped into an object reserved when allthenableAfter execution, the Promise instance willresloveAn array that contains all of these objects. If you havethenableThe status ofpending, the state of this instance is alsopending.

Specific usage:

const promise1 = new Promise((resolve) = > {
  resolve(1)})const promise2 = new Promise((resolve) = > {
  resolve(2)})const promise3 = new Promise((resolve, reject) = > {
  reject(3)})Promise.allSettled([promise1, promise2, promise3]).then(console.log)
[{status: 'depressing ', value: 1}, {status:' depressing ', value: 2}, {status: 'rejected', reason: 3}] */
Copy the code

Interface definition:

interface PromiseConstructor {
	 /**
     * Creates a Promise that is resolved with an array of results when all
     * of the provided Promises resolve or reject.
     * @param values An array of Promises.
     * @returns A new Promise.
     */
    allSettled<T extends readonly unknown[] | readonly [unknown]>(values: T):
        PromiseThe < {-readonly [P in keyof T]: PromiseSettledResult<T[P] extends PromiseLike<infer U> ? U : T[P]> }>;

    /**
     * Creates a Promise that is resolved with an array of results when all
     * of the provided Promises resolve or reject.
     * @param values An array of Promises.
     * @returns A new Promise.
     */
    allSettled<T>(values: Iterable<T>): Promise<PromiseSettledResult<T extends PromiseLike<infer U> ? U : T>[]>;
}
Copy the code

Code implementation:

class MyPromise<T> {
  static allSettled<T extends readonly unknown[] | readonly [unknown]>(
    values: T
  ): MyPromise<
    {
      -readonly [P in keyof T]: PromiseSettledResult<
        T[P] extends PromiseLike<infer U> ? U : T[P]
      >
    }
  >
  static allSettled<T>(
    values: Iterable<T>
  ): MyPromise<PromiseSettledResult<T extends PromiseLike<infer U> ? U : T>[]>
  // There is a conflict in the return value of an overloaded function
  static allSettled<T>(values: Iterable<T>): MyPromise<any> {
    // See promise.all () for general notation
    return new MyPromise((reslove) = > {
      const resultArr: any[] = []
      const doneArr: boolean[] = []
      // Get the iterator
      const iter = values[Symbol.iterator]()
      / / the current value
      let cur = iter.next()
      const resolveResult = (value: any, index: number, done? :boolean) = > {
        resultArr[index] = {
          status: Status.FULFILLED,
          value
        }
        doneArr[index] = true
        if (done && doneArr.every((item) = > item)) {
          reslove(resultArr)
        }
      }
      for (let i = 0; ! cur.done; i++) {const value = cur.value
        doneArr.push(false)
        cur = iter.next()
        if (isPromise(value)) {
          value.then(
            (value) = > {
              resolveResult(value, i, cur.done)
            },
            (reason) = > {
              // Resolve is the same as resolve
              resultArr[i] = {
                status: Status.REJECTED,
                reason
              }
              doneArr[i] = true
              if (cur.done && doneArr.every((item) = > item)) {
                reslove(resultArr)
              }
            }
          )
          // Not thenable direct storage
        } else {
          resolveResult(value, i, cur.done)
        }
      }
    })
  }
}
Copy the code

Promise.any

ESNEXT points out that it is still in experimental form and only supported by a few browsers

Promise.any(iterable) takes an iterable object and returns a Promise instance. As long as one thenable in iterable is fulfilled, the value of reslove of that Thenable will be returned, and the state of this instance will also be fulfilled. If all iterable is Thenable and all iterable is rejected, the instance is also in rejected and Reject state. An instance of type AggregateError (a subclass of Error, used to group single errors together) It’s still experimental, with only a few browsers supporting it). Essentially, this method is the opposite of promise.all ().

Specific usage:

const pErr = new Promise((resolve, reject) = > {
  reject("Always fail");
});

const pSlow = new Promise((resolve, reject) = > {
  setTimeout(resolve, 500."Finally done");
});

const pFast = new Promise((resolve, reject) = > {
  setTimeout(resolve, 100."Soon done.");
});

Promise.any([pErr, pSlow, pFast]).then((value) = > {
  console.log(value);
  // pFast fulfils first
})
// Prints: "Done soon"
Copy the code

Interface definition:

interface PromiseConstructor {
  	/**
     * The any function returns a promise that is fulfilled by the first given promise to be fulfilled, or rejected with an AggregateError containing an array of rejection reasons if all of the given promises are rejected. It resolves all elements of the passed iterable to promises as it runs this algorithm.
     * @param values An array or iterable of Promises.
     * @returns A new Promise.
     */
  any<T>(values: (T | PromiseLike<T>)[] | Iterable<T | PromiseLike<T>>): Promise<T>
}
Copy the code

Code implementation:

class MyPromise<T> {
   static any<T>(
    values: (T | PromiseLike<T>)[] | Iterable<T | PromiseLike<T>>
  ): MyPromise<T> {
    return new MyPromise((resolve, reject) = > {
      // Receive iterators
      const iter = values[Symbol.iterator]()
      let cur = iter.next()
      const doneArr: boolean[] = []
      for (let i = 0; ! cur.done; i++) {const value = cur.value
        cur = iter.next()
        doneArr.push(false)
        if (isPromise(value)) {
           // If the value is thenable, judge based on the status of thenable
          value.then(resolve, () = > {
            doneArr[i] = true
              // Only the iterator values passed in are thenable and thenable is in the rejected state
            if (cur.done && doneArr.every((item) = > item)) {
              // The Error type of AggregateError should be thrown, but since AggregateError is an experimental version, only the latest version of the browser will have it, so I'll use Error instead
              const e = new Error('All promises were rejected')
              e.stack = ' '
              reject(e)
            }
          })
        } else {
          resolve(value)
        }
      }
    })
  }
}
Copy the code

conclusion

This article uses typescript to implement a Promise from scratch based on type definitions, with a deep focus on processing then methods. It’s not perfect, but it’s got the desired effect. The author’s skills are limited. If there are any mistakes or omissions, please point them out in the comments section and ask 👍.

The code for the article has been uploaded to Github

The resources

Interviewer: “Can you make a Promise by hand?”

MDN – Promise

Promise A+ specification