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
promise
There are three state defaultspending
statepending
Can be changed intofulfilled
(Success state) orrejected
(failed state), and once the transition can not be changed into other valuespromise
There’s one insidevalue
Used to store the results of a successful statepromise
There’s one insidereason
Used to store the cause of a failed statepromise
To accept aexecutor
Function, this function takes two arguments, one isresolve
Methods, one isreject
Method when executedresolve
When,promise
The state changes tofulfilled
, the implementation ofreject
When,promise
The state changes torejected
- The default
new Promise
Internal at the time of executionexecutor
Function performs promise
Internal support for asynchronous state changespromise
Internal support for throwing exceptions, then thepromise
The state of therejected
Let’s move on to the PromiseA+ document:
promise
You have to have onethen
Method to access its currentvalue
Or is itreason
- The method takes two arguments
onFulfilled
(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 example
promise.then().then(data=>console.log(data),err=>console.log(err))
Can form a value of penetrationonFulfilled
Must be inpromise
statusfulfilled
I’m going to call it, andpromise
The inside of thevalue
The value is an argument to the function, and the function cannot be called repeatedlyonRejected
Must be inpromise
statusrejected
I’m going to call it, andpromise
The inside of thereason
The value is an argument to the function, and the function cannot be called repeatedlyonFulfilled
andonRejected
These two methods must be called after the context of the current stack has been executed, and are essentially microtasks in the event loop (setTimeout
Is a macro task, there are some differences)onFulfilled
andonRejected
These two methods must be called by a function, which means neither of them arethis.onFulfilled()
orthis.onRejected()
Call, directonFulfilled()
oronRejected()
then
Methods can be found in onepromise
Multiple calls, also known as chain calls- If the current
promise
The state of thefulfilled
Then do it in orderthen
In the methodonFulfilled
The callback- If the current
promise
The state of therejected
Then do it in orderthen
In the methodonRejected
The callbackthen
Method must return onepromise
(Next we’ll take thispromise
As thepromise2
(Be similar topromise2 = promise1.then(onFulfilled, onRejected);
- If?
onFulfilled()
oronRejected()
Either one returns a valuex
, then you have to do itresolvePromise
This function is used to process the return valuex
And then we’re going to use those values to decide what we just didthen
In the methodonFulfilled()
oronRejected()
These two callbacks returnpromise2
The state)- If we were in the
then
Performed in theonFulfilled()
oronRejected()
Method generates an exception, then thepromise2
Use the cause of the exceptione
Go to thereject
- if
onFulfilled
oronRejected
It’s not a function, andpromise
The state of thefulfilled
orrejected
So let’s use the samevalue
orreason
To 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
resolvePromise
This function is going to determinepromise2
In what state, ifx
Is a normal value, so just take itx
If thex
Is apromise
So let’s take thispromise
The state of thepromise2
The state of the- Determine if the
x
andpromise2
Is an object, i.epromise2 === x
, then you are stuck in a circular callpromise2
I’ll end up with oneTypeError
forreason
intorejected
- if
x
Is apromise
, thenpromise2
Just usex
Andx
The samevalue
Go to theresolve
Or we can use the sumx
The samereason
Go to thereject
- if
x
It’s an object or it’s a function so it’s executed firstlet then = x.then
- if
x
It’s not an object or a function soresolve
thisx
- If an error is reported in the execution of the above statement, the error cause is used
reject
promise2
- if
then
Is a function, so execute itthen.call(x,resolveCallback,rejectCallback)
- if
then
It’s not a function, soresolve
thisx
- if
x
isfulfilled
The state then goesresolveCallback
This function, at this point, will default to successvalue
As a parametery
Passed to theresolveCallback
, i.e.,y=>resolvePromise(promise2,y)
, continue to callresolvePromise
This function ensures that the return value is a normal value rather thanpromise
- if
x
isrejected
State then put the cause of this failurereason
As apromise2
The cause of failurereject
To go out- if
resolveCallback
.rejectCallback
Both 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 call
then
An exception is thrown, and ifresolveCallback
.rejectCallback
These two functions have already been called, so ignore the exception, otherwise use the exception aspromise2
thereject
why
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