I Promise you/I Promise you

Promise is an important concept in JS asynchronous programming, which abstracts objects asynchronously and is one of the more popular Javascript asynchronous programming solutions at present.

Promise/A+ is the smallest specification of Promise. including

- Promise state - then method - Promise resolution layerCopy the code

There is only one THEN method, no catch, race, all, etc. ECMAscript 6 Promise is one of the criteria that meets the Promise/A+ standard.

  • Miss ruan ES6
  • Promise/A + specification
  • Promise/A+ standard Chinese translation
  • Promise/A+ Test

Two. Concrete implementation

  1. PromiseA frameworkHere’s an example:
let p = new Promise(function(resolve, reject) {
  resolve('200');
})

p.then(data= > {
  console.log(data);
}, err => {
  console.log('err', err)
})

/ / 200
Copy the code

Analyze:

1. New Promise Returns a Promise object that accepts an 'executor' executor function and calls the function immediately. 2. Executor accepts two arguments resolve and reject, which are also functions. 3. The Promise instance has a state that defaults to 'pending' and changes to 'resolved' after the executor calls resolve. The operator calls reject, and the instance state changes to 'Rejected'. 4. Once the state of the Promise instance changes, it cannot be modified. 5. Promise instances all have 'then' methods. The then method takes two parameters. 'onResolved' successfully callback, 'onRejected' 'onResolved' in 'then' will be executed when 'executor' calls' resolve ', and 'onRejected', the second argument to the 'then' method, will be executed when 'Executor' calls' reject '.Copy the code

Implement:

// Promise three states
var PENDING = 'pending';
var RESOLVED = 'resolved';
var REJECTED = 'rejected';
function PromiseA (executor) {
  // Save this to prevent this from pointing to something else
  var _this = this; 
  // Initialize the promise value
  _this.data = undefined;
  // Initialize the state of the promise
  _this.status = PENDING;
  function resolve(value) {
    // Change the corresponding state and promise values in pending
    if(_this.status === PENDING) { _this.status = RESOLVED; _this.data = value; }}function reject(reason) {
    // Change the corresponding state and promise values in pending
    if(_this.status === PENDING) {
      _this.status = REJECTED;
      _this.data = reason;
    }
  }
  executor(resolve, reject);
}

PromiseA.prototype.then = function(onResolved, onRejected) {
  var _this = this;
  // The state is a success state, and the success callback is executed immediately, passing in its value
  if(_this.status === RESOLVED) {
    onResolved(_this.data);
  }
  // The state is a failed state, and the failed callback is executed immediately, passing in its value
  if(_this.status === REJECTED) { onRejected(_this.data); }}module.exports = PromiseA;
Copy the code
  1. Asynchronously executed, then method called multiple timesHere’s an example:
let p = new PromiseTest(function(resolve, reject) {
  setTimeout((a)= > {
    resolve('200');
  }, 1000)
})

p.then(data= > {
  console.log(1, data)
}, err => {
  console.log('err', err)
})

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

Analyze:

The result will be printed one second later, meaning that the failure and success callbacks of the THEN method are triggered after the asynchronous execution of the promise, so the state of the promise is not success or failure when the THEN method is called. The successful or failed callback functions are saved and executed after the asynchronous execution completes. The then method can be called multiple times, so arrays are needed to save

Implement:

function PromiseA (executor) {
  // ...

  // Save successful and failed callback functions
  _this.resolvedCallbacks = [];
  _this.rejectedCallbacks = [];

  // Call success function
  function resolve(value) {
    // Change the corresponding state and promise values in pending
    if(_this.status === PENDING) {
      _this.status = RESOLVED;
      _this.data = value;
      _this.resolvedCallbacks.forEach(function(fn) { fn(); }); }}// Call the failed function
  function reject(reason) {
    // Change the corresponding state and promise values in pending
    if(_this.status === PENDING) {
      _this.status = REJECTED;
      _this.data = reason;
      _this.rejectedCallbacks.forEach(function(fn) {
        fn();
      });
    }
  }
}

PromiseA.prototype.then = function(onResolved, onRejected) {
  // ...

  // The state is waiting, and the callback function is saved
  if(_this.status === PENDING) {
    _this.resolvedCallbacks.push(function() {
      onResolved(_this.data);
    })
    _this.rejectedCallbacks.push(function() { onRejected(_this.data); }}})Copy the code
  1. Error trappingHere’s an example:
let p = new PromiseA((resolve, reject) = > {throw new Error('error')});
p.then(data= > {
  console.log(data);
}, err => {
  console.log('err', err)
})
// err Error: error
Copy the code

Break it down: When a Promise goes wrong, it changes directly to the failed state and passes on the reason for the failure. Exception handling is done directly to the executor function, reject if anything goes wrong.

Implement:

function PromiseA (executor) {
  // ...
  try {
    executor(resolve, reject);
  } catch(reason) { reject(reason); }}Copy the code
  1. The then method is called chainedHere’s an example:
let p = new Promise(function(resolve, reject) {
  resolve('200');
})

p.then(data= > {
  console.log(1, data)
  throw Error('oooo')
}, err => {
  console.log('1err', err)
}).then(data= > {
  console.log(2, data);
}, err => {
  console.log('2err', err)
}).then(data= > {
  console.log(3, data)
}, err => {
  console.log('3err', err)
})
console.log('start');

// start
/ / 1 '200'
// 2err Error: oooo
// 3 undefined
Copy the code

Analyze:

  1. Promise is an asynchronous execution function. So the first printstart, the use ofsetTimeoutEnsure the order of execution.
  2. Promise instance callthenMethod, returns a new Promise instance,
  3. The result of a successful or failed Promise execution is passed to the next Promise instancethenmethodsonResolvedoronRejectedParameters for the callback.
  4. The Promise instance is invoked chainedthenWhen any one of themthenExecution error occurs when the chain is called nextthenWill execute the wrong callback,
  5. The return value is undefinedundefined, which executes a successful callback, as above3 undefined.

Implement:


function PromiseA (executor) {
  // ...
  function resolve(value) {
  // Change the corresponding state and promise values in pending
    setTimeout(function() {
      if(_this.status === PENDING) {
        _this.status = RESOLVED;
        _this.data = value;
        _this.resolvedCallbacks.forEach(function(fn) { fn(); }); }})}function reject(reason) {
  // Change the corresponding state and promise values in pending
    setTimeout(function() {
      if(_this.status === PENDING) {
        _this.status = REJECTED;
        _this.data = reason;
        _this.rejectedCallbacks.forEach(function(fn) {
          fn();
        });
      }
    })
  }
}

PromiseA.prototype.then = function(onResolved, onRejected) {
  var _this = this;
  var promise2;
  promise2 = new PromiseA(function(resolve, reject) {
    // execute asynchronously to ensure the order of calls
    setTimeout(function() {
      // The state is a success state, and the success callback is executed immediately, passing in its value
      if(_this.status === RESOLVED) {
        // the then method executes exception processing, rejecting the error
        try {
          var x = onResolved(_this.data);
          resolvePromise(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason)
        }
      }
      // The state is a failed state, and the failed callback is executed immediately, passing in its value
      if(_this.status === REJECTED) {
        var x = onRejected(_this.data);
        resolvePromise(promise2, x, resolve, reject);
      }
    
      // The state is waiting, and the callback function is saved
      if(_this.status === PENDING) {
        _this.resolvedCallbacks.push(function() {
          var x = onResolved(_this.data);
          resolvePromise(promise2, x, resolve, reject); 
        })
        _this.rejectedCallbacks.push(function() {
          varx = onRejected(_this.data); resolvePromise(promise2, x, resolve, reject); })}})})return promise2;
}

function resolvePromise(promise2, x, resolve, reject) {
  resolve(x);
}
Copy the code
  1. The value of the penetrationHere’s an example:
let p = new Promise(function(resolve, reject) {
  resolve('200');
})

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

Analyze: When the previous THEN does not pass the callback parameter or the parameter is null, you need to pass the value to the next THEN method. The then method has two optional parameters onResolved and onRejected. Leave the then parameter blank and let the value pass through.

Implement:

PromiseA.prototype.then = function(onResolved, onRejected) {
  onResolved = typeof onResolved === 'function' ? onResolved : function(value) {return value};
  onRejected = typeof onRejected === 'function' ? onREjected : function(reason) {throw reason};
  
  // ...
}
Copy the code
  1. A circular referenceHere’s an example:
let p = new Promise(function(resolve, reject) {
  resolve('200');
})

var p2 = p.then((a)= > {
  return p2;
})

p2.then((a)= > {
  console.log(1)
}, err => {
  console.log('err1', err)
})
// err1 TypeError: Chaining cycle detected for promise
Copy the code

If p’s then method calls itself, it will generate a loopback

Implement:

function resolvePromise(promise2, x, resolve, reject) {
  if(promise2 === x) {
    return reject(new TypeError('Circular reference')); }}// ...
Copy the code
  1. ResolvePromise function implementationThe Promise/A+ specification involved in resolvePromise
    • Call each Promise instancethenThe new Promise instance, called promise2, will be returnedthenThe value returned by the callback is calledx
    • Promise2 does not workxFor the same object, waiting for itself, circular reference.
    • ifxIs an object or function and is notnull, just to getxThen method of, ifxIs the object that preventsxIs through theObject.definePropertyaddthenProperty, and add get and set listeners. If an exception is thrown in a listener, it needs to be caught.x.thenPhi is a function, let’s sayxIs an instance of Promise, implemented directlyxthenMethod, the success of the execution of thepromise2If you succeed, you failpromise2Failure, ifx.thenIf it is not a functionxIs a normal value and is called directlypromise2resolveMethod will bexIf the condition is not met, the return value is a normal value and is executed directlypromise2resolveAnd pass x as a parameter;
    • If every timexthenMethod, the argument passed in the callback is once again an instance of Promise, repeating over and over again, recursivelyresolvePromiseparsing
    • In the recursive process, the inner and outer layers are called at the same timeresolverejectShould declare an identity variablecalledMake a judgment call to avoid this

Implement:

function resolvePromise(promise2, x, resolve, reject) {
  var then;
  // To avoid multiple calls
  var called = false;

  if(promise2 === x) {
    return reject(new TypeError('Loop callback'));
  }

  // if x is a normal value (not object or function), resolve
  if(x ! = =null && (typeof x === 'object' || typeof x === 'function')) {// Each promise has a then method, which uses _THEN to prevent errors and a try catch
    try {
      then = x.then;
      if(typeof then === 'function') {
        // Make sure this refers to x
        then.call(x, function(y) {
          if(called) return;
          called = true;
          return resolvePromise(promise2, y, resolve, reject);
        }, function(e) {
          if(called) return;
          called = true;
          returnreject(e); })}else{ resolve(x); }}catch (err) {
      if(called) return;
      called = true; reject(err); }}else{ resolve(x); }}Copy the code

Three. Test it out

Promises – aplus-Tests: Promises – aplus-Tests: Promises – Aplus-Tests: Promises – Aplus-Tests: Promises – Aplus-Tests

PromiseA.deferred = PromiseA.defer = function() {
  var dfd = {}
  dfd.promise = new PromiseA(function(resolve, reject) {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}
Copy the code

4. Extended methods

  1. Catch methodHere’s an example:
let p = new Promise(function(resolve, reject) {
  resolve('200');
})

p.then(data= > {
    throw new Error('eeee');
}, err => {
    console.log('err', err)
}).catch(err= > {
    console.log(err)
})
// Error eeee
Copy the code

Implement:

PromiseA.prototype.catch = function(onRejected) {
  return this.then(null, onRejected);
}
Copy the code
  1. Resolve methodHere’s an example:
let p = Promise.resolve('200');
p.then(data= > {
    console.log(data)
}, err => {
    console.log('err', err)
})
/ / 200
Copy the code

Implement:

Promise.prototype.resolve = function(value) {
  return new Promise(function(resolve, reject) { resolve(value); })}Copy the code
  1. Reject methodHere’s an example:
let p = Promise.reject('eeee');

p.then(data= > {
    console.log(data)
}, err => {
    console.log('err', err)
})
// err eeee
Copy the code

Implement:

Promise.reject = function(reason) {
  return new Promise(function(resolve, reject) {
    reject(reason)
  })
}
Copy the code
  1. Race methodHere’s an example:
let p1 = new Promise(function(resolve, reject) {
  setTimeout((a)= > {
    resolve(1)},1000)})let p2 = new Promise(function(resolve, reject) {
  setTimeout((a)= > {
    resolve(2)},2000)})let p3 = new Promise(function(resolve, reject) {
  setTimeout((a)= > {
    resolve(3)},3000)})let p = Promise.race([p1, p2, p3])
p.then(data= > {
  console.log(data)
}, err => {
  console.log('err', err)
})

/ / 1
Copy the code

Implement:

Promise.race = function(promises) {
  return new Promise(function(resolve, reject) {
    promises.forEach(function(promise) {
      promise.then(resolve, reject)
    }) 
  })
}
Copy the code

Five. All the code



// Promise three states
var PENDING = 'pending';
var RESOLVED = 'resolved';
var REJECTED = 'rejected';

/** * * @param {function} executor */
function PromiseA (executor) {
  // Save this to prevent this from pointing to something else
  var _this = this; 

  // Initialize the promise value
  _this.data = undefined;

  // Initialize the state of the promise
  _this.status = PENDING;

  // Save successful and failed callback functions
  _this.resolvedCallbacks = [];
  _this.rejectedCallbacks = [];

  // Call success function
  function resolve(value) {
    // Change the corresponding state and promise values in pending
    setTimeout(function() {
      if(_this.status === PENDING) {
        _this.status = RESOLVED;
        _this.data = value;
        _this.resolvedCallbacks.forEach(function(fn) { fn(); }); }})}// Call the failed function
  function reject(reason) {
    // Change the corresponding state and promise values in pending
    setTimeout(function() {
      if(_this.status === PENDING) {
        _this.status = REJECTED;
        _this.data = reason;
        _this.rejectedCallbacks.forEach(function(fn) { fn(); }); }})}New PromiseA((resolve, reject) => {throw new Error(' Error ')})
  try {
    executor(resolve, reject);
  } catch (reason) {
    reject(reason)
  }
}

/** ** @param {promise} promise2 then Returns a new promise object * @param {*} x Return value of onResolved in promise * @param {*} resolve Resolve method of promise2 * @param {*} reject Method of promise2 */
function resolvePromise(promise2, x, resolve, reject) {
  var then;
  // To avoid multiple calls
  var called = false;

  if(promise2 === x) {
    return reject(new TypeError('Loop callback'));
  }

  // if x is a normal value (not object or function), resolve
  if(x ! = =null && (typeof x === 'object' || typeof x === 'function')) {// Each promise has a then method, which uses _THEN to prevent errors and a try catch
    try {
      then = x.then;
      if(typeof then === 'function') {
        // Make sure this refers to x
        then.call(x, function(y) {
          if(called) return;
          called = true;
          return resolvePromise(promise2, y, resolve, reject);
        }, function(e) {
          if(called) return;
          called = true;
          returnreject(e); })}else{ resolve(x); }}catch (err) {
      if(called) return;
      called = true; reject(err); }}else{ resolve(x); }}/** * @param {function} onResolved error */
PromiseA.prototype.then = function(onResolved, onRejected) {
  var _this = this;
  var promise2;
  // Value penetration
  onResolved = typeof onResolved === 'function' ? onResolved : function(value) {return value};
  onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {throw reason};

  return promise2 = new PromiseA(function(resolve, reject) {
    // execute asynchronously to ensure the order of calls
    setTimeout(function() {
      // The state is a success state, and the success callback is executed immediately, passing in its value
      if(_this.status === RESOLVED) {
        // For the inside
        try {
          var x = onResolved(_this.data);
          resolvePromise(promise2, x, resolve, reject);
        } catch(reason) { reject(reason); }}// The state is a failed state, and the failed callback is executed immediately, passing in its value
      if(_this.status === REJECTED) {
        try {
          var x = onRejected(_this.data);
          resolvePromise(promise2, x, resolve, reject);
        } catch(reason) { reject(reason); }}// The state is waiting, and the callback function is saved
      if(_this.status === PENDING) {
        _this.resolvedCallbacks.push(function() {
          try {
            var x = onResolved(_this.data);
            resolvePromise(promise2, x, resolve, reject); 
          } catch (reason) {
            reject(reason);
          }
        })
        _this.rejectedCallbacks.push(function() {
          try {
            var x = onRejected(_this.data);
            resolvePromise(promise2, x, resolve, reject);
          } catch (reason) {
            reject(reason)
          }
        })
      }
    })
  })
}

PromiseA.prototype.catch = function(onRejected) {
  return this.then(null, onRejected);
}

PromiseA.resolve = function(value) {
  return new PromiseA(function(resolve, reject) {
    resolve(value);
  })
}

PromiseA.reject = function(reason) {
  return new PromiseA(function(resolve, reject) {
    reject(reason)
  })
}

PromiseA.race = function(promises) {
  return new PromiseA(function(resolve, reject) {
    promises.forEach(function(promise) {
      promise.then(resolve, reject)
    }) 
  })
}

// Use Promises -aplus-tests
PromiseA.deferred = PromiseA.defer = function() {
  var dfd = {}
  dfd.promise = new PromiseA(function(resolve, reject) {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}

module.exports = PromiseA;
Copy the code

Six Reference.

  • Miss ruan ES6
  • Promise/A + specification
  • Promise/A+ standard Chinese translation
  • Promise/A+ Test
  • Parse the Promise internals