This time, thoroughly understand Promise

Promise must be one of three states: wait state (Pending), execution state (Fulfilled) and rejected states (Rejected). Once Promise is resolve or reject, can not migrate to any other state (i.e., state immutable).

The basic process:

  1. Promise initialization state (pending)

  2. Execution then (..) to register a callback processing array (then the method can be invoked with a promise many times)

  3. Promise of execution immediately passed fn function, the internal Promise resolve, reject function passed as an argument to fn, according to the timing of the event handling mechanism

  4. Promise where the key is to ensure, then the incoming parameters onFulfilled method and onRejected, it must be performed in the new execution stack after that round then the event loop method is called in.

The real chained Promise means that after the current promise fulfilled to reach the state, which began the next promise.

Chained calls

Promise start to look at the results, the following piece of code:

 new Promise((resolve, reject) => {
 setTimeout(() => {
 resolve({ test: 1 })
 resolve({ test: 2 })
 reject({ test: 2 })
 }, 1000)
 }).then((data) => {
 console.log('result1', data)
 },(data1)=>{
 console.log('result2',data1)
 }).then((data) => {
 console.log('result3', data)
 })
 //result1 { test: 1 }
 //result3 undefined
复制代码

Obviously this output different data. It can be seen that:

  1. Calls can be chained, and then returned each time a new Promise (2 times print results are inconsistent, if it is the same instance, print the results should be consistent.

  2. Output only resolve the first content, the content reject is no output, i.e., there is a state and state Promise only be made pending - rejected> fulfilled or pending->, it is irreversible.

  3. then returned in a new Promise, but then registered a callback still belong on the Promise.

Based on the above, we first write-based PromiseA + Promise model specification contains only resolve method:

 Promise function (Fn) {  
 the let State = 'Pending'; 
 the let value = null; 
 const the callbacks = []; 
 this.then = function (onFulfilled) { 
 return new new Promise ((Resolve, Reject) => { 
 handle ({// bridge, the new method will resolve the Promise, callback object placed before a promise of 
 onFulfilled,  
 resolve 
 }) 
 }) 
 } 
 function handle (the callback) { 
 IF (State === 'Pending') { 
 callbacks.push (the callback) 
 return ; 
 } 
 
 IF (State === 'Fulfilled') { 
 IF {(callback.onFulfilled!) 
 callback.resolve (value) 
 return; 
 } 
 const callback.onFulfilled RET = (value) // handling callbacks  
 callback.resolve (ret) / next / promise to resolve a process
 } 
 }
 function resolve(newValue){
 const fn = ()=>{
 if(state !== 'pending')return
 state = 'fulfilled';
 value = newValue
 handelCb()
 }
 
 setTimeout(fn,0) //基于 PromiseA+ 规范
 }
 
 function handelCb(){
 while(callbacks.length) {
 const fulfiledFn = callbacks.shift();
 handle(fulfiledFn);
 };
 }
 
 fn(resolve)
 }
复制代码

The model is simple and easy to understand, Promise, this is the most critical point in the then newly created, its status is changed to node fulfilled in the callback on a Promise of execution when completed. That is when a state Promise is fulfilled, it will execute the callback function, and the results returned by the callback function will be treated as value, return (Promise is then generated) to the next Promise, while the next Promise the state will also be changed (executed resolve or reject), then go execute its callback, and so on down the chain effect ... call came out.

However, if only in the case of the example, we can write:

 new Promise((resolve, reject) => {
 setTimeout(() => {
 resolve({ test: 1 })
 }, 1000)
 }).then((data) => {
 console.log('result1', data)
 //dosomething
 console.log('result3')
 })
 //result1 { test: 1 }
 //result3
复制代码

In fact, we used to call the chain, is used in an asynchronous callback to solve the problem "callback hell". Examples are as follows:

Promise new new ((Resolve, Reject) => { 
 the setTimeout (() => { 
 Resolve ({Test:. 1}) 
 }, 1000) 
}) the then ((Data) => {. 
 the console.log ( 'RESULT1', Data ) 
 // doSomething 
 return Test () 
}). the then ((Data) => { 
 the console.log ( 'result2', Data) 
}) 
function Test (ID) { 
 return new new Promise (((Resolve) => { 
 the setTimeout ( () => { 
 Resolve ({Test: 2}) 
 }, 5000) 
 })) 
} 
// Promise based on the first model, and outputs the execution 
// Test RESULT1 {:}. 1 
// {result2 the then Promise: ƒ } 
copy the code

Promise using the above model, the result is obviously not what we want. Seriously look at the above model, the implementation of callback.resolve, incoming parameter is returned callback.onFulfilled execution is complete, it is clear this is a test case returns Promise, Promise and resolve methods our model is not special treatment. Then we will resolve change it:

 Promise function (Fn) {  
 ... 
 function Resolve (newValue) { 
 const Fn = () => { 
 IF (State! == 'Pending') return 
 IF (newValue && (typeof newValue === 'Object' || typeof === newValue 'function')) { 
 const = {} newValue the then 
 IF (typeof the then === 'function') { 
 // newValue Promise is newly generated at this time on a promise to resolve the resolve 
 // equivalent the method then calls the newly generated Promise injected on a promise to resolve its callback 
 then.call (newValue, resolve) 
 return 
 } 
 } 
 State = 'Fulfilled'; 
 value = newValue 
 handelCb () 
 } 
 
 the setTimeout (Fn, 0) 
 } 
 ... 
 } 
copy the code

With this model, then test our example, we get the correct result:

 new Promise((resolve, reject) => {
 setTimeout(() => {
 resolve({ test: 1 })
 }, 1000)
 }).then((data) => {
 console.log('result1', data)
 //dosomething
 return test()
 }).then((data) => {
 console.log('result2', data)
 })
 function test(id) {
 return new Promise(((resolve, reject) => {
 setTimeout(() => {
 resolve({ test: 2 })
 }, 5000)
 }))
 }
 //result1 { test: 1 }
 //result2 { test: 2 }
复制代码

Obviously, the new logic is aimed resolve the processing parameters for the Promise of the time. We look Promise test inside created, then it is not called the method. From the above analysis we already know Promise callback function is registered by calling its methods then, so test Promise which created its callback function is empty.

Obviously if there is no callback function, when executed resolve, the chain is no way to go. Therefore, we need to take the initiative to infuse the callback function.

As long as we resolve to perform the function first then produced Promise, the delay to the test inside the Promise of state when onFulfilled re-executed, the chain can be continued. So, when resolve the parameters for the Promise of the time, then call its method for injecting a callback function, which is injected before a Promise resolve method, so use the call to bind the point of this.

Promise based on the new model, and examples Promise callback generated during the execution of the above can be used to the following table:

Promise callback P1 [{onFulfilled: c1 (fn then the first), resolve: p2resolve}] P2 (P1 generated when the call then) [{onFulfilled: c2 (then in a second fn), resolve: p3resolve} ] P3 (P2 generated the call then) [] P4 (c1 generated execution [call test]) [{onFulfilled: p2resolve, resolve: p5resolve}] P5 (when calling p2resolve, into the generated logic then.call) [] has this table, we can clearly know the order of execution of various examples callback is:

c1 -> p2resolve -> c2 -> p3resolve -> [] -> p5resolve -> []

These are the principles of chained calls.

reject

Here let us reject the full complement logic. Only you need to register a callback, coupled logic state to reject the change.

The complete code is as follows:

 function Promise(fn){ 
 let state = 'pending';
 let value = null;
 const callbacks = [];
 this.then = function (onFulfilled,onRejected){
 return new Promise((resolve, reject)=>{
 handle({
 onFulfilled, 
 onRejected,
 resolve, 
 reject
 })
 })
 }
 function handle(callback){
 if(state === 'pending'){
 callbacks.push(callback)
 return;
 }
 
 const cb = state === 'fulfilled' ? callback.onFulfilled:callback.onRejected;
 const next = state === 'fulfilled'? callback.resolve:callback.reject;
 if(!cb){
 next(value)
 return;
 }
 const ret = cb(value)
 next(ret)
 }
 function resolve(newValue){
 const fn = ()=>{
 if(state !== 'pending')return
 if(newValue && (typeof newValue === 'object' || typeof newValue === 'function')){
 const {then} = newValue
 if(typeof then === 'function'){
 // newValue 为新产生的 Promise,此时resolve为上个 promise 的resolve
 //相当于调用了新产生 Promise 的then方法,注入了上个 promise 的resolve 为其回调
 then.call(newValue,resolve, reject)
 return
 }
 }
 state = 'fulfilled';
 value = newValue
 handelCb()
 }
 
 setTimeout(fn,0)
 }
 function reject(error){
 const fn = ()=>{
 if(state !== 'pending')return
 if(error && (typeof error === 'object' || typeof error === 'function')){
 const {then} = error
 if(typeof then === 'function'){
 then.call(error,resolve, reject)
 return
 }
 }
 state = 'rejected';
 value = error
 handelCb()
 }
 setTimeout(fn,0)
 }
 function handelCb(){
 while(callbacks.length) {
 const fn = callbacks.shift();
 handle(fn);
 };
 }
 fn(resolve, reject)
 }
复制代码

异常处理

异常通常是指在执行成功/失败回调时代码出错产生的错误,对于这类异常,我们使用 try-catch 来捕获错误,并将 Promise 设为 rejected 状态即可。

handle代码改造如下:

 function handle(callback){
 if(state === 'pending'){
 callbacks.push(callback)
 return;
 }
 
 const cb = state === 'fulfilled' ? callback.onFulfilled:callback.onRejected;
 const next = state === 'fulfilled'? callback.resolve:callback.reject;
 if(!cb){
 next(value)
 return;
 }
 try {
 const ret = cb(value)
 next(ret)
 } catch (e) {
 callback.reject(e);
 } 
 }
复制代码

我们实际使用时,常习惯注册 catch 方法来处理错误,例:

 new Promise((resolve, reject) => {
 setTimeout(() => {
 resolve({ test: 1 })
 }, 1000)
 }).then((data) => {
 console.log('result1', data)
 //dosomething
 return test()
 }).catch((ex) => {
 console.log('error', ex)
 })
复制代码

实际上,错误也好,异常也罢,最终都是通过reject实现的。也就是说可以通过 then 中的错误回调来处理。所以我们可以增加这样的一个 catch 方法:

 function Promise(fn){ 
 ...
 this.then = function (onFulfilled,onRejected){
 return new Promise((resolve, reject)=>{
 handle({
 onFulfilled, 
 onRejected,
 resolve, 
 reject
 })
 })
 }
 this.catch = function (onError){
 this.then(null,onError)
 }
 ...
 }
复制代码

Finally方法

在实际应用的时候,我们很容易会碰到这样的场景,不管Promise最后的状态如何,都要执行一些最后的操作。我们把这些操作放到 finally 中,也就是说 finally 注册的函数是与 Promise 的状态无关的,不依赖 Promise 的执行结果。所以我们可以这样写 finally 的逻辑:

 function Promise(fn){ 
 ...
 this.catch = function (onError){
 this.then(null,onError)
 }
 this.finally = function (onDone){
 this.then(onDone,onDone)
 }
 ...
 }
复制代码

resolve 方法和 reject 方法

实际应用中,我们可以使用 Promise.resolve 和 Promise.reject 方法,用于将于将非 Promise 实例包装为 Promise 实例。如下例子:

Promise.resolve({name:'winty'})
Promise.reject({name:'winty'})
// 等价于
new Promise(resolve => resolve({name:'winty'}))
new Promise((resolve,reject) => reject({name:'winty'}))
复制代码

这些情况下,Promise.resolve 的入参可能有以下几种情况:

  • 无参数 [直接返回一个resolved状态的 Promise 对象]

  • 普通数据对象 [直接返回一个resolved状态的 Promise 对象]

  • 一个Promise实例 [直接返回当前实例]

  • 一个thenable对象(thenable对象指的是具有then方法的对象) [转为 Promise 对象,并立即执行thenable对象的then方法。]

基于以上几点,我们可以实现一个 Promise.resolve 方法如下:

 function Promise(fn){ 
 ...
 this.resolve = function (value){
 if (value && value instanceof Promise) {
 return value;
 } else if (value && typeof value === 'object' && typeof value.then === 'function'){
 let then = value.then;
 return new Promise(resolve => {
 then(resolve);
 });
 } else if (value) {
 return new Promise(resolve => resolve(value));
 } else {
 return new Promise(resolve => resolve());
 }
 }
 ...
 }
复制代码

Promise.reject与Promise.resolve类似,区别在于Promise.reject始终返回一个状态的rejected的Promise实例,而Promise.resolve的参数如果是一个Promise实例的话,返回的是参数对应的Promise实例,所以状态不一 定。 因此,reject 的实现就简单多了,如下:

 function Promise(fn){ 
 ...
 this.reject = function (value){
 return new Promise(function(resolve, reject) {
				reject(value);
			});
 }
 ...
 }
复制代码

Promise.all

入参是一个 Promise 的实例数组,然后注册一个 then 方法,然后是数组中的 Promise 实例的状态都转为 fulfilled 之后则执行 then 方法。这里主要就是一个计数逻辑,每当一个 Promise 的状态变为 fulfilled 之后就保存该实例返回的数据,然后将计数减一,当计数器变为 0 时,代表数组中所有 Promise 实例都执行完毕。

 function Promise(fn){ 
 ...
 this.all = function (arr){
 var args = Array.prototype.slice.call(arr);
 return new Promise(function(resolve, reject) {
 if(args.length === 0) return resolve([]);
 var remaining = args.length;
 function res(i, val) {
 try {
 if(val && (typeof val === 'object' || typeof val === 'function')) {
 var then = val.then;
 if(typeof then === 'function') {
 then.call(val, function(val) {
 res(i, val);
 }, reject);
 return;
 }
 }
 args[i] = val;
 if(--remaining === 0) {
 resolve(args);
 }
 } catch(ex) {
 reject(ex);
 }
 }
 for(var i = 0; i < args.length; i++) {
 res(i, args[i]);
 }
 });
 }
 ...
 }
复制代码

Promise.race

有了 Promise.all 的理解,Promise.race 理解起来就更容易了。它的入参也是一个 Promise 实例数组,然后其 then 注册的回调方法是数组中的某一个 Promise 的状态变为 fulfilled 的时候就执行。因为 Promise 的状态只能改变一次,那么我们只需要把 Promise.race 中产生的 Promise 对象的 resolve 方法,注入到数组中的每一个 Promise 实例中的回调函数中即可。

function Promise(fn){ 
 ...
 this.race = function(values) {
 return new Promise(function(resolve, reject) {
 for(var i = 0, len = values.length; i < len; i++) {
 values[i].then(resolve, reject);
 }
 });
 }
 ...
 } 
复制代码

总结

Promise 源码不过几百行,我们可以从执行结果出发,分析每一步的执行过程,然后思考其作用即可。其中最关键的点就是要理解 then 函数是负责注册回调的,真正的执行是在 Promise 的状态被改变之后。而当 resolve 的入参是一个 Promise 时,要想链式调用起来,就必须调用其 then 方法(then.call),将上一个 Promise 的 resolve 方法注入其回调数组中。

补充说明

虽然 then 普遍认为是微任务。但是浏览器没办法模拟微任务,目前要么用 setImmediate ,这个也是宏任务,且不兼容的情况下还是用 setTimeout 打底的。还有,promise 的 polyfill (es6-promise) 里用的也是 setTimeout。因此这里就直接用 setTimeout,以宏任务来代替微任务了。

参考资料

  • PromiseA+规范

  • Promise 实现原理精解

  • 30分钟,让你彻底明白Promise原理

完整 Promise 模型

function Promise(fn) {
 let state = 'pending'
 let value = null
 const callbacks = []
 this.then = function (onFulfilled, onRejected) {
 return new Promise((resolve, reject) => {
 handle({
 onFulfilled,
 onRejected,
 resolve,
 reject,
 })
 })
 }
 this.catch = function (onError) {
 this.then(null, onError)
 }
 this.finally = function (onDone) {
 this.then(onDone, onError)
 }
 this.resolve = function (value) {
 if (value && value instanceof Promise) {
 return value
 } if (value && typeof value === 'object' && typeof value.then === 'function') {
 const { then } = value
 return new Promise((resolve) => {
 then(resolve)
 })
 } if (value) {
 return new Promise(resolve => resolve(value))
 }
 return new Promise(resolve => resolve())
 }
 this.reject = function (value) {
 return new Promise(((resolve, reject) => {
 reject(value)
 }))
 }
 this.all = function (arr) {
 const args = Array.prototype.slice.call(arr)
 return new Promise(((resolve, reject) => {
 if (args.length === 0) return resolve([])
 let remaining = args.length
 function res(i, val) {
 try {
 if (val && (typeof val === 'object' || typeof val === 'function')) {
 const { then } = val
 if (typeof then === 'function') {
 then.call(val, (val) => {
 res(i, val)
 }, reject)
 return
 }
 }
 args[i] = val
 if (--remaining === 0) {
 resolve(args)
 }
 } catch (ex) {
 reject(ex)
 }
 }
 for (let i = 0; i < args.length; i++) {
 res(i, args[i])
 }
 }))
 }
 this.race = function (values) {
 return new Promise(((resolve, reject) => {
 for (let i = 0, len = values.length; i < len; i++) {
 values[i].then(resolve, reject)
 }
 }))
 }
 function handle(callback) {
 if (state === 'pending') {
 callbacks.push(callback)
 return
 }
 const cb = state === 'fulfilled' ? callback.onFulfilled : callback.onRejected
 const next = state === 'fulfilled' ? callback.resolve : callback.reject
 if (!cb) {
 next(value)
 return
 }
 try {
 const ret = cb(value)
 next(ret)
 } catch (e) {
 callback.reject(e)
 }
 }
 function resolve(newValue) {
 const fn = () => {
 if (state !== 'pending') return
 if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
 const { then } = newValue
 if (typeof then === 'function') {
 // newValue 为新产生的 Promise,此时resolve为上个 promise 的resolve
 // 相当于调用了新产生 Promise 的then方法,注入了上个 promise 的resolve 为其回调
 then.call(newValue, resolve, reject)
 return
 }
 }
 state = 'fulfilled'
 value = newValue
 handelCb()
 }
 setTimeout(fn, 0)
 }
 function reject(error) {
 const fn = () => {
 if (state !== 'pending') return
 if (error && (typeof error === 'object' || typeof error === 'function')) {
 const { then } = error
 if (typeof then === 'function') {
 then.call(error, resolve, reject)
 return
 }
 }
 state = 'rejected'
 value = error
 handelCb()
 }
 setTimeout(fn, 0)
 }
 function handelCb() {
 while (callbacks.length) {
 const fn = callbacks.shift()
 handle(fn)
 }
 }
 fn(resolve, reject)
}

这一次,彻底弄懂 Promise


Guess you like

Origin blog.51cto.com/14516511/2437097