preface

Although this year has been 18 years, today we will continue to talk about ES6. ES6 has been a few years, but how much do we know about the grammar of ES6? Will use? Or proficient? I believe that everyone and I have a heart to improve their own, for the new toys can not only understand, for the idea is the most attractive, so the next through an article, to let everyone for the Promise of this toy do master degree!!

Open a bottle of ice here

Promise

Promise is a solution to asynchronous programming that makes more sense and is more powerful than traditional solutions — callback functions and events. It was first proposed and implemented by the community, and ES6 has written it into the language standard, unifying usage, and providing Promise objects natively.

Hic ~ ~ ~ ~ ~

First of all, Pormise is a solution that we can see from the literal, and there are also two traditional solutions · callback functions and events, ok, so let’s talk about those two solutions first.

The Callback function Callback

The callback function is passed to another function as an argument and executed after certain conditions are met. For example, if we want to implement a function to calculate the sum of 1 to 5 after three seconds, then:

// Sum functionfunction sum () {
        return eval([...arguments].join('+'} // Execute the function after three secondsfunction asyncGetSum (callback) {
        setTimeout(function(){var result = callback(1,2,3,4,5); console.log(result) },3000) } asyncGetSum(sum);Copy the code

This implementation is a callback function, but if I want to implement an animation, the animation is executed by moving the ball 100px to the right, then 100px down, and 100px to the left, and each animation lasts for 3s.

    dom.animate({left:'100px'}, 3000,'linear'.function(){
        dom.animate({top:'100px'}, 3000,'linear'.function(){
            dom.animate({left:'0px'}, 3000,'linear'.function(){
                console.log('the animation done')})})})Copy the code

You’ll see a callback nesting, or callback hell, that makes your code very unreadable.

The event

Event processing is the ON binding event and trigger triggering event in jQuery. In fact, it is our common publish and subscribe mode. When I subscribe to an event, I am the subscriber.

// Define a release centerletPublishCenter = {subscribeArrays:{}, // define a subscriber callback to subscribe:function(key,callback){// Add subscribersif(! this.subscribeArrays[key]){ this.subscribeArrays[key] = []; } this.subscribeArrays[key].push(callback) }, publish:function(){// Publish the first argument is keylet params = [...arguments];
            let key = params.shift();
            let callbacks = this.subscribeArrays[key];
            if(! Callbacks | | callbacks. Length = = = 0) {/ / if no one subscription Then it returnsreturn false
            }
            for( leti = 0 ; i < callbacks.length; i++ ){ callbacks[i].apply( this, params ); }}}; // Subscribe a wantWatermelon event publishCenter.subscribe('wantWatermelon'.function(){console.log('Watermelon!'// Trigger wantWatermelon publishCenter.publish('wantWatermelon')
Copy the code


Just like watermelon

Promise A+

It is good that we have Pormise (Pormise). Now let’s understand the principle of Promise more deeply by implementing a Promise. Let’s start with the PromiseA+, which is a specification that governs the way promises are made, in order for them to be error-free and follow the desired process.

Characteristics of Promise

Let’s take a step-by-step look at what promises are based on the PromiseA+ documentation.

First of all, let’s look at section 2.1 of the document. The title is “Promise States”, which refers to the Promise states.

  • A promise has only three states, namely, pending, fulfilled and rejected.
  • When a promise is pending, it may become a pity or Rejected
  • Once the state of the promise is fulfilled, the state can no longer be changed, and an immutable value needs to be provided
  • Once the state of promise is changed to Rejected, the state cannot be changed and an immutable reason needs to be provided

Ok, so let’s start writing our own promises. Let’s start with a normal Promise

// Success or failure requires a value or reasonletPromise1 = new Promise((resolve,rejected)=>{// Promise1 = new Promise((resolve,rejected)=>{// The internal callback function (usually called the executor) immediately executes console.log('hahahha'); // Promise internally supports asynchronysetTimeout(function(){
            resolve(123);
        },100)
        // throw new Error('error') We can also throw an error inside the actuator and the promise becomes the Rejected state})Copy the code

Based on the code above and the state specification in the PromiseA+ specification, we know that promises already have the following features

  1. promiseThere are three state defaultspendingstatependingCan be changed intofulfilled(Success state) orrejected(failed state), and once the transition can not be changed into other values
  2. promiseThere’s one insidevalueUsed to store the results of a successful state
  3. promiseThere’s one insidereasonUsed to store the cause of a failed state
  4. promiseTo accept aexecutorFunction, this function takes two arguments, one isresolveMethods, one isrejectMethod when executedresolveWhen,promiseThe state changes tofulfilled, the implementation ofrejectWhen,promiseThe state changes torejected
  5. The defaultnew PromiseInternal at the time of executionexecutorFunction performs
  6. promiseInternal support for asynchronous state changes
  7. promiseInternal support for throwing exceptions, then thepromiseThe state of therejected

Let’s move on to the PromiseA+ document:

  • promiseYou have to have onethenMethod to access its currentvalueOr is itreason
  • The method takes two argumentsonFulfilled(Successfully callback function),onRejected(Failed callback function)promise.then(onFulfilled, onRejected)
  • Both of these parameters are optional. If you find that they are not function types, ignore the examplepromise.then().then(data=>console.log(data),err=>console.log(err))Can form a value of penetration
  • onFulfilledMust be inpromisestatusfulfilledI’m going to call it, andpromiseThe inside of thevalueThe value is an argument to the function, and the function cannot be called repeatedly
  • onRejectedMust be inpromisestatusrejectedI’m going to call it, andpromiseThe inside of thereasonThe value is an argument to the function, and the function cannot be called repeatedly
  • onFulfilledandonRejectedThese two methods must be called after the context of the current stack has been executed, and are essentially microtasks in the event loop (setTimeoutIs a macro task, there are some differences)
  • onFulfilledandonRejectedThese two methods must be called by a function, which means neither of them arethis.onFulfilled()orthis.onRejected()Call, directonFulfilled()oronRejected()
  • thenMethods can be found in onepromiseMultiple calls, also known as chain calls
  • If the currentpromiseThe state of thefulfilledThen do it in orderthenIn the methodonFulfilledThe callback
  • If the currentpromiseThe state of therejectedThen do it in orderthenIn the methodonRejectedThe callback
  • thenMethod must return onepromise(Next we’ll take thispromiseAs thepromise2(Be similar topromise2 = promise1.then(onFulfilled, onRejected);
  • If?onFulfilled()oronRejected()Either one returns a valuex, then you have to do itresolvePromiseThis function is used to process the return valuexAnd then we’re going to use those values to decide what we just didthenIn the methodonFulfilled()oronRejected()These two callbacks returnpromise2The state)
  • If we were in thethenPerformed in theonFulfilled()oronRejected()Method generates an exception, then thepromise2Use the cause of the exceptioneGo to thereject
  • ifonFulfilledoronRejectedIt’s not a function, andpromiseThe state of thefulfilledorrejectedSo let’s use the samevalueorreasonTo updatepromise2(In fact, this one and the third reason, that is, it is worth penetrating the problem)

Okay, so we’ve got all these specification features, so let’s use them for practice

/** * implement a PromiseA+ * @description implement a brief promise * @param {Function} executor * @author Leslie */function Promise(executor){
        let self = this;
        self.status = 'pending'; // Store promise status pending pity. Self. value = undefined; Self. reason = undefined; / / record the cause of the failure self. OnfulfilledCallbacks = []; Self. onrejectedCallbacks = []; // Collect failed callback when asyncfunction resolve(value){
            if(self.status === 'pending'){
                self.status = 'fulfilled'; // Change the promise state self.value = value; // Call resolve after async executionthenThe success callback function performs the self again. OnfulfilledCallbacks. ForEach element (= > {element ()}); }}function reject(reason){
            if(self.status === 'pending'){
                self.status = 'rejected'; Reject = reject; reject = reject; Reject (reject); reject (reject); reject (rejectthenFailure of the callback function performs the self again. OnrejectedCallbacks. ForEach element (= > {element ()}); }} // If the executor throws an exception,reject the promise state with the exception. } catch (error) { reject(error) } } Promise.prototype.then =function(onfulfilled,onrejected){
        // onfulfilled thenMethod // onRejectedthenFailed callback in methodletself = this; // If ondepressing is not a function, then replace it with the default function to achieve the value'function'? onfulfilled:val=>val; // If onRejected is not a function then replace it with the default function so that the value can penetrate onRejected = typeof onRejected ==='function'? onrejected: err=>{throw err}let promise2 = new Promise((resolve,reject)=>{
            if(self.status === 'fulfilled'{/ / to joinsetTimeout simulates asynchronous // if calledthenThis will be a pity. Then the success callback will be called and the parameter will be passed as the value of successsetTimeout(function(){// If an exception occurs during a callback, use that exception as the cause of the promise2 failure. Try {// x is the result of a successful callbackletx = onfulfilled(self.value); / / call resolvePromise function according to the value of x to determine the status of promise2 resolvePromise (promise2, x, resolve, reject); } catch (error) { reject(error) } },0) }if(self.status === 'rejected'{/ / to joinsetTimeout simulates asynchronous // if calledthenThe failed callback is called and the argument is Passed as failed ReasonsetTimeout(function(){// If a callback fails, use that exception as the cause of the promise2 failure. Try {// x is the result of a failed callbackletx = onrejected(self.reason); / / call resolvePromise function according to the value of x to determine the status of promise2 resolvePromise (promise2, x, resolve, reject); } catch (error) { reject(error) } },0) }if(self.status === 'pending'){// if calledthenThe promise state is still pending, indicating that the promsie executor executes resolve or reject asynchronouslythenMethod the success callback callbacks and failure to store, waiting for the promise of state into a fulfilled or rejected to execute sequentially related callback self. OnfulfilledCallbacks. Push (() = > {/ /setTimeout Simulates asynchronoussetTimeout(function(){// If an exception occurs during a callback, use that exception as the cause of the promise2 failure. Try {// x is the result of a successful callbackletX = onfulfilled (self value) / / call resolvePromise function according to the value of x to determine the status of promise2 resolvePromise (promise2, x, resolve, reject); } catch (error) { reject(error) } },0) }) self.onrejectedCallbacks.push(()=>{ //setTimeout Simulates asynchronoussetTimeout(function(){// If a callback fails, use that exception as the cause of the promise2 failure. Try {// x is the result of a failed callbackletX = onrejected (self. "reason) / / call resolvePromise function according to the value of x to determine the status of promise2 resolvePromise (promise2, x, resolve, reject); } catch (error) { reject(error) } },0) }) } })return promise2;
    }

Copy the code

In one go, do not feel before summed up the characteristics of very effective, very smooth to the characteristics of the finished code ~

So what else is in the promiseA+ documentation

  • resolvePromiseThis function is going to determinepromise2In what state, ifxIs a normal value, so just take itxIf thexIs apromiseSo let’s take thispromiseThe state of thepromise2The state of the
  • Determine if thexandpromise2Is an object, i.epromise2 === x, then you are stuck in a circular callpromise2I’ll end up with oneTypeErrorforreasonintorejected
  • ifxIs apromise, thenpromise2Just usexAndxThe samevalueGo to theresolveOr we can use the sumxThe samereasonGo to thereject
  • ifxIt’s an object or it’s a function so it’s executed firstlet then = x.then
  • ifxIt’s not an object or a function soresolvethisx
  • If an error is reported in the execution of the above statement, the error cause is usedreject promise2
  • ifthenIs a function, so execute itthen.call(x,resolveCallback,rejectCallback)
  • ifthenIt’s not a function, soresolvethisx
  • ifxisfulfilledThe state then goesresolveCallbackThis function, at this point, will default to successvalueAs a parameteryPassed to theresolveCallback, i.e.,y=>resolvePromise(promise2,y), continue to callresolvePromiseThis function ensures that the return value is a normal value rather thanpromise
  • ifxisrejectedState then put the cause of this failurereasonAs apromise2The cause of failurerejectTo go out
  • ifresolveCallback.rejectCallbackBoth functions have already been called, or called multiple times with the same arguments, so make sure you call only the first time and ignore the rest
  • If the callthenAn exception is thrown, and ifresolveCallback.rejectCallbackThese two functions have already been called, so ignore the exception, otherwise use the exception aspromise2therejectwhy

We’ve summed up so much again and again, so let’s just masturbate.

/** * is used for processingthenMethod returns the result wrapped as a promise to facilitate chained calls to * @param {*} promise2thenMethod execution produces promises to facilitate chained calls * @param {*} xthenThe resolve method returned by result * @param {*} resolve is used to change the final state of the promise * @param {*} reject The reject method of the returned promise is used to change the final state of the promise */functionResolvePromise (promise2, x, resolve, reject) {/ / first determines whether x and promise2 is the same reference If you use is a type error as reject Promise2 failure reasonsif( promise2 === x) return reject(new TypeError('typeError: Dude, you're looping! ')); // Called is used to record the state change of promise2. Once the state changes, it is not allowed to change to another statelet called;
    if( x ! == null && ( typeof x ==='object' || typeof x === 'function'// If x is an object or a function, it may be a promise. Note that null typeof is also an objectthenIf this step fails, reject the exception. Try {let then= x.then; // Prevent others from writing errorsif(typeof then= = ='function'If) {/ /thenIt's a function so call itthenIf x is a promise and the final state succeeds, the successful callback is executed, the failed callback is executed, and if it fails, the cause of the failure is rejected as the cause of the failure of the promise2, Call (x,y => {(x,y => {(x,y => {));if(called) return;
                    called = true;
                    resolvePromise(promise2,y,resolve,reject)
                }, error=>{
                    if(called) return
                    called = true;
                    reject(error)
                })
            }else{
                resolve(x)
            }
        } catch (error) {
            if(called) return
            called = true;
            reject(error)
        }
    }elseResolve resolve(x)}}Copy the code

Finnnnnnally, we’ve finally made a Promise based on the PromiseA+ specification!

Finally in order to perfect, we need to realize the promise on this promise. The resolve, promise. Reject, and the catch, promise. All and promise. Race these methods.

Some of Promise’s methods

Promise.resolve = function(value){
    return new Promise((resolve,reject)=>{
        resolve(value)
    })
}
Promise.reject = function(reason){
    return new Promise((resolve,reject)=>{
        reject(reason)
    })
}
Promise.prototype.catch = function(onRejected){
    return this.then(null,onRejected)
}
Promise.all = function(promises){
    return new Promise((resolve,reject)=>{
        let arr = [];
        let i = 0;
        function getResult(index,value){
            arr[index] = value;
            if(++i == promises.length) {
                resolve(arr)
            }
        }
        for(leti = 0; i<promises.length; i++){ promises[i].then(data=>{ getResult(i,data) },reject) } }) } Promise.race =function(promises){
    return new Promise((resolve,reject)=>{
        for(let i = 0 ; i < promises.length ; i++){
            promises[i].then(resolve,reject)
        }
    })
}
Copy the code

Promise syntactic sugar

Syntactic candy was made to make writing promises faster, so let’s take a look at an example where we encapsulate a width and height function that reads images asynchronously

// The original waylet getImgWidthHeight = function(imgUrl){
        return new Promise((resolve,reject)=>{
            let img = new Image();
            img.onload = function(){
                resolve(img.width+The '-'+img.height)
            }
            img.onerror = function(e){ reject(e) } img.src = imgUrl; })}Copy the code

Do you feel how to write up a little comfortable but a little uncomfortable, as if I have to write the actuator every time ah! Why! Ok, no reason, since it is uncomfortable we will change!

// Implement a promise syntax promise.defer = promise.deferred =function() {let dfd = {};
    dfd.promise = new Promise((resolve,reject)=>{
        dfd.resolve = resolve;
        dfd.reject = reject;
    })
    return dfd
}
Copy the code

So with the syntax sugar above let’s take a look at the function of that picture what do we do

    let newGetImgWidthHeight = function(imgUrl){
        let dfd = Promise.defer();
        let img = new Image();
        img.onload = function(){
            dfd.resolve(img.width+The '-'+img.height)
        }
        img.onerror = function(e){
            dfd.reject(e)
        }
        img.url = imgUrl;
        return dfd.promise
    }
Copy the code

Did you find that we are missing a layer of function nesting

The final test

npm install promises-aplus-tests -g
Copy the code

Promise-aplus-tests: Promises are promise-tests, promises are promise-tests, promises are promise-tests, promises are promise-tests, promises are promise-tests, promises are promise-tests Run our promise when the installation is complete

We all passed the test! Cool! And a chicken leg for dinner