Preliminary knowledge

  • The callback function
  • Senior function
  • Publish and subscribe
  • Promise A + specification

What is a promise? What does it do

Promise is a solution to asynchronous programming that solves the problem of asynchronous callback hell and prevents the unmaintainability of program code caused by layers of nesting. Since it is convenient, it is necessary to learn its principles and underlying implementation, so I write a simple Promise according to the PromiseA+ specification and implement promise.all (), promise.race () and other apis

  • Solve callback hell
  • Resolve multiple callback functions to synchronize results

A few methods of promise

  • promise.all()
  • promise.race()
  • promise.resolve()
  • promise.reject()

Three states of promise

  • Wait states pending
  • State successfully resolved
  • Failed states rejected

The characteristics of the promise

  • 1. Executor automatically executes new by default
  • 2. Every instance of a Promise has a THEN method
  • 3. The then method has two parameters: a successful callback and a failed callback
  • 4. The THEN method is asynchronous (microtasks) // microtasks are executed before macro tasks
  • 5. An instance of the same Promise can then be repeated, calling all successful methods on success and all failed methods on failure
  • 6. Asynchronous behavior can be supported in new Promises
  • 7. You go into failure mode if you find a mistake

The next input needs the last output (with dependencies)

  • 1. If another promise is returned after a promise is executed, the execution result of this promise will be passed to the next THEN
  • 2. If a normal value is returned instead of a promise in the THEN, the normal value will be used as the result of the success of the next THEN
  • 3. If the current THEN fails, the next THEN fails
  • 4. If undefined is returned, the next success will be achieved regardless of the current success or failure
  • 5. Catch is when an error is not handled
  • 6. You can write nothing in then, which is equivalent to writing nothing.

Promise A + specification

  • The original
  • translation
  • Check the pluginnpm install promises-aplus-tests -gUse it to check that your promise meets the promise specification

Simple implementations need to be perfected

    function Promise(executor){
    let self = this;
    self.value = undefined; // Parameters for success
    self.reason = undefined; // Parameters for failure
    self.status = 'pending'; / / state
    self.onResolvedCallbacks = [];// Store successful callbacks in THEN
    self.onRejectedCallbacks = []; // Store failed callbacks in then
    function resolve(value){ // 
        if(self.status === 'pending'){
            self.value = value;
            self.status = 'resolved';
            self.onResolvedCallbacks.forEach(fn= >fn()); }}function reject(reason){
        if(self.status === 'pending'){
            self.reason = reason;
            self.status = 'rejected';
            self.onRejectedCallbacks.forEach(fn= >fn()); }}// The function fails if an exception occurs during execution
    try{
        executor(resolve,reject);
    }catch(e){ reject(e); }}Promise.prototype.then = function(onFulfilled,onRejected){
    let self = this;
    if(self.status === 'resolved'){
        onFulfilled(self.value);
    }
    if(self.status === 'rejected'){
        onRejected(self.reason);
    }
    if(self.status === 'pending') {// Save the callback function
        self.onResolvedCallbacks.push((a)= >{
            onFulfilled(self.value);
        });
        self.onRejectedCallbacks.push((a)= >{ onRejected(self.reason) }); }}module.exports = Promise;
Copy the code

Basic implementation

function Promise(executor) {
    let self = this;
    self.value = undefined; // Success value
    self.reason = undefined; // Cause of failure
    self.status = 'pending'; // The value is pending
    self.onResolvedCallbacks = []; // It is possible that the new Promise will have an asynchronous operation, saving successful and failed callbacks
    self.onRejectedCallbacks = [];
    function resolve(value) { // Change the status to success
        if (self.status === 'pending') { // Only the wait state can change the state
            self.value = value;
            self.status = 'resolved';
            self.onResolvedCallbacks.forEach(fn= >fn()); }}function reject(reason) { // Change the state to failed
        if (self.status === 'pending') {
            self.reason = reason;
            self.status = 'rejected';
            self.onRejectedCallbacks.forEach(fn= >fn()); }}try {
        // By default new Promise should execute the corresponding executor (synchronous execution)
        executor(resolve, reject);
    } catch (e) { // If an error occurs while exectuor is executed, the current promise will failreject(e); }}/** ** @param {*} return value of promise2 THEN (new promise returned) * @param {*} x then return value of success or failure function * @param {*} resolve Resolve * @param {*} reject */
// All promises follow this specification (all promises are generic)

function resolvePromise(promise2,x,resolve,reject){
    // the same object is returned when the function is executed
    if(promise2 === x){ 
        return reject(new TypeError('Chaining cycle'));
    }
    let called;
    // x can be either a promise or a normal value
    if(x! = =null && (typeof x=== 'object' || typeof x === 'function')) {try{
            let then = x.then; // How can I get an exception from an attribute on an object? (This promise may not have been written by the person who made it, but it may have been written by someone else.)
            // x may still be a promise
            // {then:{}}
            // The logic here is not just yours, but someone else's promise may fail as well as succeed
            if(typeof then === 'function'){
                then.call(x,y=>{ // Return a successful result after promise
                    // Recurse until resolved to a normal value
                    if(called) return; // Prevent multiple calls
                    called = true;
                    // Recursion may result in a promise that needs to be resolved in a loop
                    resolvePromise(promise2,y,resolve,reject);
                },err=>{ // The result of a failed promise
                    if(called) return;
                    called = true;
                    reject(err);
                });
            }else{ resolve(x); }}catch(e){
            if(called) return;
            called = true; reject(e); }}else{ If x is a constantresolve(x); }}// Then calls are asynchronous (the success or failure of the native THEN is a microtask)
Promise.prototype.then = function (onFulfilled, onRejected) {
    // Successful and failed callbacks are optional
    
    // This is a big pity, onFulfilled // This is a big pity
    let self = this;
    let promise2;
    // Return a new promise every time you call then
    promise2 = new Promise((resolve, reject) = > {
        if (self.status === 'resolved') {
            setTimeout((a)= >{
                try {
                    // An exception may occur when a successful callback is executed as the result of a promise2 error
                    let x = onFulfilled(self.value);
                    // The result may be a PROMISE after executing the current successful callback
                    resolvePromise(promise2,x,resolve,reject);
                } catch(e) { reject(e); }},0)}if (self.status === 'rejected') {
            setTimeout((a)= >{
                try {
                    let x = onRejected(self.reason);
                    resolvePromise(promise2,x,resolve,reject);
                } catch(e) { reject(e); }},0)}if (self.status === 'pending') {
            self.onResolvedCallbacks.push((a)= > {
                setTimeout((a)= >{
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2,x,resolve,reject);
                    } catch(e) { reject(e); }},0)}); self.onRejectedCallbacks.push((a)= > {
                setTimeout((a)= >{
                    try {
                        let x = onRejected(self.reason);
                        resolvePromise(promise2,x,resolve,reject);
                    } catch(e) { reject(e); }},0)}); }});return promise2
}
// Why add setTimeout (specification required)

Promise.defer = Promise.deferred = function(){
    let dfd = {};
    dfd.promise = new Promise((resolve,reject) = >{
        dfd.resolve = resolve;
        dfd.reject = reject;
    })
    return dfd;
}
module.exports = Promise;
Copy the code

The final version

function Promise(executor) {
    let self = this;
    self.value = undefined; 
    self.reason = undefined; 
    self.status = 'pending';
    self.onResolvedCallbacks = [];
    self.onRejectedCallbacks = [];
    function resolve(value) { 
        if (self.status === 'pending') {
            self.value = value;
            self.status = 'resolved';
            self.onResolvedCallbacks.forEach(fn= >fn()); }}function reject(reason) { 
        if (self.status === 'pending') {
            self.reason = reason;
            self.status = 'rejected';
            self.onRejectedCallbacks.forEach(fn= >fn()); }}try {
        executor(resolve, reject);
    } catch(e) { reject(e); }}function resolvePromise(promise2,x,resolve,reject){
    if(promise2 === x){
        return reject(new TypeError('Chaining cycle'));
    }
    let called;
    if(x! = =null && (typeof x=== 'object' || typeof x === 'function')) {try{
            let then = x.then; 
            if(typeof then === 'function'){
                then.call(x,y=>{ 
                    if(called) return; 
                    called = true;
                    resolvePromise(promise2,y,resolve,reject);
                },err=>{ 
                    if(called) return;
                    called = true;
                    reject(err);
                });
            }else{ resolve(x); }}catch(e){
            if(called) return;
            called = true; reject(e); }}else{ If x is a constantresolve(x); }}Promise.prototype.then = function (onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function'? onFulfilled:val= >val;
    onRejected = typeof onRejected === 'function'? onRejected:err= >{throw err}
    let self = this;
    let promise2;
    promise2 = new Promise((resolve, reject) = > {
        if (self.status === 'resolved') {
            setTimeout((a)= >{
                try {
                    let x = onFulfilled(self.value);
                    resolvePromise(promise2,x,resolve,reject);
                } catch(e) { reject(e); }},0)}if (self.status === 'rejected') {
            setTimeout((a)= >{
                try {
                    let x = onRejected(self.reason);
                    resolvePromise(promise2,x,resolve,reject);
                } catch(e) { reject(e); }},0)}if (self.status === 'pending') {
            self.onResolvedCallbacks.push((a)= > {
                setTimeout((a)= >{
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2,x,resolve,reject);
                    } catch(e) { reject(e); }},0)}); self.onRejectedCallbacks.push((a)= > {
                setTimeout((a)= >{
                    try {
                        let x = onRejected(self.reason);
                        resolvePromise(promise2,x,resolve,reject);
                    } catch(e) { reject(e); }},0)}); }});return promise2
}
// The syntax sugar is intended to solve the promise nesting problem q.derfer ()
Promise.defer = Promise.deferred = function(){
    let dfd = {};
    dfd.promise = new Promise((resolve,reject) = >{
        dfd.resolve = resolve;
        dfd.reject = reject;
    })
    return dfd;
}
// the method on the class
Promise.reject = function(reason){
    return new Promise((resolve,reject) = >{ reject(reason); })}Promise.resolve = function(value){
    return new Promise((resolve,reject) = >{ resolve(value); })}Promise.prototype.catch = function(onRejected){
    // Write fails by default
    return this.then(null,onRejected);
};
/ / all methods
Promise.all = function(promises){
    return new Promise((resolve,reject) = >{
        let arr = [];
        let i = 0;
        function processData(index,data){
            arr[index] = data;
            if(++i == promises.length){ resolve(arr); }}for(let i = 0; i<promises.length; i++){ promises[i].then(data= >{ // Data is the result of successprocessData(i,data); },reject); }})}// Whichever request is quickest
Promise.race = function(promises){
    return new Promise((resolve,reject) = >{
        for(let i = 0;i<promises.length;i++){
            promises[i].then(resolve,reject);
        }
    })
}
module.exports = Promise;
Copy the code