手把手教你按照PromiseA+规范来实现Promise

该实现是按照promiseA+规范来进行梳理的 当使用promise的时候须要先new一个实例,因此咱们要构造一个promise构造函数jquery

let p = new Promise(function(resolve, reject){
    // 先调谁,就走谁
    reject('不对了');  // 失败
    resolve(1);  // 成功  

    // 若是抛出异常也会走到then的失败函数里
    throw new Error('错了'); 
});
p.then(function(data) {
    console.log(data);    
}, function(err) {
    console.log(err);      // '不对了'
});
复制代码

new的Promise实例包含一个执行函数(executor),该函数有两个参数,分别是成功和失败的方法npm

这里要记住一点是成功和失败的方法是互斥的,同时调用时,先调用的先执行,后面调用的不会再执行segmentfault

状态

都知道promise是有三种状态值的数组

  • pending(待定) 默认的状态
  • fulfilled(成功)
  • rejected(失败)

那么给构造函数写一个status,用来保存状态的变化 pendding能够转为fulfilled或者rejected, 一旦转换了,status就不会改变了,要么是成功态,要么是失败态promise

// 实现promise
function Promise(executor) {
	const self = this;
	self.status = 'pending';	// 默认的状态,pending->fulfilled, pending->rejected
	self.value = null;		// 成功的值
	self.reason = null;		// 失败的缘由

	function resolve(value) {	// 成功
	    if (self.status === 'pending') {
		self.status = 'fulfilled';
		self.value = value;
	    }
	}
	function reject(reason) {		// 失败
	    if (self.status === 'pending') {
		self.status = 'rejected';
		self.reason = reason;
            }
	}


	try {
	    executor(resolve, reject);
	} catch(e) {		// 若是抛出错误,就直接走失败的方法
	    reject(e);
	}
}

Promise.prototype.then = function(onFulfilled, onRejected) {
	const self = this;
	if (self.status === 'fulfilled') {
	    onFulfilled();
	}
	if (self.status === 'rejected') {
	    onRejected();
	}
};

module.exports = Promise;
复制代码

上面写了不少,可是发现都是处理同步的状况,当遇到异步的时候该怎么处理呢,咱们继续往下写bash

异步promise

promise中能够写异步代码,而且能够进行屡次then异步

let p = new Promise(function(resolve, reject){
    // 异步操做
    setTimeout(function() {
        resolve('ok');
    }, 1000);
}).then(function(data) {
    console.log(data);    // 'ok'
    return `下面的人接着  + ${data}`;
}, function(err) {
    console.log(err);     
}).then(function(data) {
    console.log(data);    // '下面的人接着 ok'
}, function(err) {
    console.log(err);     
});
复制代码
实现一下异步的promise

promise实例能够屡次then,当成功后会将then中的成功的方法依次执行, 咱们能够先将then中成功的回调和失败的回调存到数组内,当成功时调用成功的数组便可函数

function Promise() {
    self.status = 'pending';
    self.value = null;
    self.reason = null;
+   self.onFulfilledCb = [];    // 存放then成功的回调
+   self.onRejectedCb = [];     // 存放then失败的回调   

    function resolve(value) {
        if (self.status === 'pending') {
            self.status = 'fulfilled';
            self.value = value;
     +      self.onFulfilledCb.forEach(function (fn) {
                fn();
            });
        }
    }
    
    function reject(reason) {
        if (self.status === 'pending') {
            self.status = 'rejected';
            self.reason = reason;
      +     self.onRejectedCb.forEach(function (fn) {
                fn();
            });
        }
    }  

    try {
        executor(resolve, reject);
    } catch(e) {        // 若是抛出错误,就直接走失败的方法
        reject(e);
    }
}

Promise.prototype.then = function(onFulfilled, onRejected) {
    const self = this;
    if (self.status === 'fulfilled') {
        onFulfilled(self.value);
    } 
    if (self.status === 'rejected') {
        onRejected(self.reason);
    }
    // 当调用then时 可能没成功也没失败,就处于pending状态

+   if (self.status === 'pending') {
        // 将成功的回调添加到数组中
        self.onFulfilledCb.push(function () {
            onFulfilled(self.value);
        });
        self.onRejectedCb.push(function () {
            onRejected(self.reason);
        });
    }
};

module.exports = Promise;
复制代码

链式调用

上面的代码实现了异步调用的时候then方法是怎么处理的状况,测试

那么接下来就来到重要的环节了,你们都知道promise的then方法也是支持链式调用的ui

不过then的链式调用可不一样于jquery返回this的方式实现的

☆️ 它是返回了一个新的promise

下面来看代码

let p = new Promise(function(resolve, reject) {
        resolve('ok');
});
p.then(function (data) {
    console.log(data);
    return data;
}, function (err) {
    console.log(err);
    // return '有返回值就走成功态'
}).then(function (data) {
    console.log('2 '+data);
}, function (err) {
    console.log(err);
})
复制代码

now让咱们开始实现链式调用,仍是老样子,+号表明实现的主要代码:

Promise.prototype.then = function (onFulfilled, onRejected) {
    const self = this;
 +  let promise2;    // 定义一个promise2变量

    if (self.status === 'fulfilled') {
         // 返回一个新的promise
 +      promise2 = new Promise(function (resolve, reject) {
            // x多是个普通值,也多是个promise
 +          let x = onFulfilled(self.value);
            // x也多是别人写的promise,写一个函数统一去处理
 +          resolvePromise(promise2, x, resolve, reject);
        });
    }
    if (self.status === 'rejected') {
 +       promise2 = new Promise(function (resolve, reject) {
 +          let x = onRejected(self.reason);
 +          resolvePromise(promise2, x, resolve, reject);
        });
    }
    // 当调用then时 可能没成功也没失败,就处于pending状态
    if (self.status === 'pending') {
        // 将成功的回调添加到数组中
+        promise2 = new Promise(function (resolve, reject) {
            self.onFulfilledCb.push(function () {
+               let x = onFulfilled(self.value);
+               resolvePromise(promise2, x, resolve, reject);
            });

            self.onRejectedCb.push(function () {
+               let x = onRejected(self.reason);
+               resolvePromise(promise2, x, resolve, reject);
            });
        });
    }

    return promise2;
};
function resolvePromise(p2, x, resolve, reject) {
    if (p2 === x) {    // 不能返回本身
        return reject(new TypeError('循环引用'));
    }
    let called;     // 表示是否调用成功or失败
    // x返回的多是对象和函数也多是一个普通的值
    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        try {
            let then = x.then;

            if (typeof then === 'function') {
                then.call(x, function (y) {
                    // 防止屡次调用
                    if (called) return;
                    called = true;
                    // y可能仍是个promise,因此递归继续解析直到返回一个普通值
                    resolvePromise(p2, y, resolve, reject);
                }, function (e) {
                    if (called) return;
                    called = true;
                    reject(e);
                });
            } else {    
                // 处理then不是函数的状况,如{then: 1},就直接返回成功
                resolve(x);
            }
        } catch(e) {
            if (called) return;
            called = true;
            reject(e);
        }
    } else {
        resolve(x);     // 返回一个普通值
    }
}
复制代码

经过以上代码也能够得出了一些结论

  • 若是then中不管是成功的回调仍是失败的回调有return返回值,都会走下一个then的成功
  • 若是第一个promise返回了一个普通值,会进入下一次then的成功回调;若是第一个promise返回了一个新的promise,须要等待返回promise执行的结果传递给下一次的then中
  • resolvePromise函数,返回的结果和promise是同一个,既不会成功也不会失败,咱们就报一个类型错误提示一下

以上咱们已经实现了promise的链式调用了,但这还不够promise有一种状况是在then中什么都不传的状况,还继续链式调用

let p = new Promise(function (resolve, reject) {
    reject(999);
});
p.then().then().then(function (data) {
    console.log(data);  // 999    
}, function (err) {
    console.log(err);
});
复制代码

这就是promise中值的穿透,该实现是在then中,那么咱们也对then方法加以改良去实现一下

Promise.prototype.then = function(onFulfilled, onRejected) {
+    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function (value) {
        return value
    };
+    onRejected = typeof onRejected === 'function' ? onFulfilled : function (err) {
        throw err
    };  
    const self = this;
    let promise2;
};
复制代码

其实很简单,就是在then的时候若是你不给我传回调函数,那么我就判断一下,确实没有传,那我就给你新建立一个函数而后在成功回调的时候把值传到下一个then中去

另外在promise规范中要求,全部的onFulfilled和onRejected都须要异步执行,因此咱们要给他们加上异步,显然这个更容易实现了,看下面代码

Promise.prototype.then = function(onFulfilled, onRejected) {
    if (self.status === 'fulfilled') {
        promise2 = new Promise(function (resolve, reject) {
            // 当成功或失败执行时,有异常那么返回的promise应该处于失败态
            // 用try/catch来防止返回异常的状况
            // 加上setTimeout实现异步调用
+           setTimeout(function () {
                try {
                    let x = onFulfilled(self.value);
                    resolvePromise(promise2, x, resolve, reject);
                } catch(e) {
                    reject(e);
                }
            });
        });
    }
    if (self.status === 'rejected') {
        promise2 = new Promise(function (resolve, reject) {
+           setTimeout(function () {
                try {
                    let x = onRejected(self.reason);
                    resolvePromise(promise2, x, resolve, reject);
                } catch(e) {
                    reject(e);
                }
            });
        });
    }
};
    if (self.status === 'pending') {
        // 将成功的回调添加到数组中
        promise2 = new Promise(function (resolve, reject) {
            self.onFulfilledCb.push(function () {
+               setTimeout(function () {
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch(e) {
                        reject(e);
                    }
                });
            });
           
            self.onRejectedCb.push(function () {
+               setTimeout(function () {
                    try {
                        let x = onRejected(self.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch(e) {
                        reject(e);
                    }
                });
        });
    }
    
    return promise2;
复制代码

好了,咱们以上就实现了promise中最重要的then方法了,写的很差请多理解吧。

不过既然把then都写完了,那接下来再写几个其余的,写多点也让你们一块儿来研究研究。

其余方法

捕获错误的catch方法

先来看下用法,其实这个和then方法里失败的回调onRejected是同样的

let p = new Promise(function(resolve, reject) {
    reject('错错错');
});
p.then(function(data){
    console.log(data);
}).catch(function(e){
    console.log(e);    // '错错错'
});
复制代码

下面上代码:

// 捕获错误的方法
Promise.prototype.catch = function(callback) {
     // 也是调用then方法,给成功的回调传一个null,给失败的回调传入callback
    return this.then(null, callback); 
};
复制代码

Promise类自身也有一些方法,经常使用的有all, race, resolve, reject

都写到这里了,那就继续写下去,废话很少说,开撸!!!

all方法

Promise.all()的用法

let p = new Promise(function(resolve, reject) {
   setTimeout(function() {
        resolve('北京');
    }, 1000);
});
let p2 = new Promise(function(resolve, reject) {
    setTimeout(function() {
        resolve('南京');
    }, 200);
});
let p3 = new Promise(function(resolve, reject) {
    resolve('东京');
});
Promise.all([p, p2, p3]).then(function(data) {
    // all方法是全部的promise都成功后才会成功
    // 按照传入all里的顺序依次执行,p里面的定时器只是执行完成的时间而已,不影响all里输出顺序
    // 如:p这个须要等1秒后才会返回成功态,p2就须要等待1秒时间
    console.log(data);    // [ '北京', '南京', '东京' ]
});
复制代码

上面知道了用法,下面就来写实现,客官且留步继续看下去

Promise.all = function(items) {
    return new Promise(function(resolve, reject) {
        let res = [];    // 用来存储成功函数返回的值
        let num = 0;  // 记录都返回成功的数字
        let len = items.length;  // 数组的长度 
        // 对数组进行遍历
        for (let i = 0; i < len; i++) {
              items[i].then(function(data) {
                  res[i] = data;
                  if (++num === len) {
                      resolve(res);
                  }
              }, reject);
        }
    });
};
复制代码

如此这般,这般如此,就实现了all

再接再砺还有race方法

那么race方法顾名思义,race就是比赛,就是在比谁快,谁先成功,返回的就是谁的成功数据

race方法

来看一下怎么用

let p = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve('北京');
    }, 1000);
});
let p2 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve('南京');
    }, 500);
});
let p3 = new Promise(function (resolve, reject) {
    setTimeout(function () {
        resolve('东京');
    });
});
Promise.race([p, p2, p3]).then(function (data) {
    // p3最早执行完返回成功,因此data就为p3的值,其余的p,p2都不返回
    console.log(data);  // '东京'
});
复制代码

那么,不啰嗦了,写吧

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

以上就实现了race方法,那么一气呵成再写下resolve和reject吧

这两个方法看起来就很是简单,由于以前的铺垫已经很好了基本属于直接用的状态 仍是先来看下用法吧

resolve和reject方法
// 成功
Promise.resolve([1,2,3,4]).then(function(data) {
    console.log(data);  // [1,2,3,4]
});
// 失败
Promise.reject('err!').then(function () {}, function (err) {
    console.log(err);   // 'err!'
});
复制代码

来,快点,开写,不犹豫

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

完成!!!

各位客官,是否是以为感受身体被掏空了,写了这么多终于完事了,能够来梳理一下了。 错了,还有最后一个方法没写,它就是defer

defer方法的做用是什么呢?

首先它不须要写new来生成promise实例了,

其次是由于promise的测试库promises-aplus-tests须要写这么一个方法,哈哈哈

看下怎么用吧,其实和Q是同样的

function read() {
    let defer = Promise.defer();  // Q里写的是Q.defer()
    require('fs').readFile('./2.promise/1.txt', 'utf8', function (err, data) {
        if(err) defer.reject(err);
        defer.resolve(data);
    })
    return defer.promise;
}
read().then(function (data) {
    console.log(data)
},function(err){
    console.log(err);
})
复制代码

为了测试库的须要,实现最后一个吧

Promise.defer = Promise.deferred = function() {
    let def = {};
    def.promise = new Promise(function(resolve, reject) {
        def.resolve = resolve;
        def.reject = reject;
    });

    return def;
}
复制代码

终于,完成了全部的实现了。实属不易,也谢谢你们坚持看完,但愿有所收获吧!哈哈

  • 最后的最后,当各位客官也在尝试着实现promise的时候
  • 别忘记把写完的代码进行测试一下
  • promiseA+规范也有一个专门的测试库来跑通你实现的promise到底如何
    • 全局安装 npm i promises-aplus-tests -g
    • promises-aplus-tests 项目.js
  • 若是跑彻底是√,那么恭喜你,你又多了一项技能了
相关文章
相关标签/搜索