Promise(中文意思:承诺)对象是ES6带来的一个新的异步编程的解决方案,与传统的回调函数更加合理强大。
概念
Promise 对象是一个代理对象(代理一个值),被代理的值在Promise对象创建时可能是未知的。它允许你为异步操作的成功和失败分别绑定相应的处理方法(handlers)。 这让异步方法可以像同步方法那样返回值,但并不是立即返回最终执行结果,而是一个能代表未来出现的结果的promise对象。
状态
一个 Promise有以下几种状态:
- pending:初始的状态。
- fulfilled:成功。
- rejected:失败。
(上面都讲了啥啊?半句没有听懂,磨刀中...ing)
举个栗
(各位大哥不要着急......是我错了,下面这个例子包你懂)
假设我有一个女朋友(好吧,我没有,程序猿是不可能有女朋友的!!!),她明天生日,今天中午我答应明天肯定帮她庆生给她惊喜,那么我答应她的这个承诺就开始进入等待状态(pending),等待着明天的到来。如果明天我按时陪她吃饭给她礼物惊喜,完成了这个承诺,那这个承诺的状态就由等待状态变成了成功完成状态(fulfilled),这个承诺一旦完成了就有了结果了,那就不会有其他状态了,也就是承诺的状态不会再改变了;但如果明天我工作太忙要加班忘记了,没有完成这个承诺,那状态也就由等待状态变成了失败状态(rejected),状态也不会再有改变了(基本女朋友也没有了...)。
Promise大致就是干了怎么一件事件,懂了撒?
基本用法
ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。
基本用法一
let p = new Promise((resolve, reject) => {
setTimeout(() => { // 异步操作
if(true) { // 成功
resolve('我是异步返回的成功数据');
}else { // 失败
reject('我是异步返回的失败数据');
}
}, 1000)
console.log('我先输入'); // 构造函数立刻执行
})
p.then(res => {
console.log(res); // 我是异步返回的成功数据
}, err => {
console.log(err); //我是异步返回的失败数据
})
基本用法二
改用了.catch来捕获异常,这估计才是比较常用的方式吧?反正小编更偏向这种写法。。。
let p = new Promise((resolve, reject) => {
setTimeout(() => { // 异步操作
if(true) { // 成功
resolve('我是异步返回的成功数据');
}else { // 失败
reject('我是异步返回的失败数据');
}
}, 1000)
console.log('我先输入'); // 构造函数立刻执行
})
p.then(res => {
console.log(res); // 我是异步返回的成功数据
}).catch(err => {
console.log(err); //我是异步返回的失败数据
})
实现手动Promise
同步手写版(面试手写够用^-^)
简单回忆了下基本用法,下面我们就赶紧步入正题。
我们以构造函数的形式来搞它吧,有大佬也有用ES6的Class的形式来实现的,感兴趣的小伙伴可以自行某度看看。
首先把Promise构造函数定义出来。
function myPromise() {
}
观察上面基本用法,能知道构造函数接收一个函数作为参数,并且该函数还是立刻执行的(console.log('我先输入'); // 构造函数立刻执行),且函数还接收另外两个回调函数作为参数(Promise对象其实本质还是利用回调函数的语法糖来工作)。
function myPromise(constructor) {
function resolve() {
}
function reject() {
}
constructor(resolve, reject);
}
然后我们看到.then()方法,它能通过构造函数的实例访问到,很明显我们得在构造函数的原型上创建这个方法才行(原型?请点),且它接收两个函数(看上面 基本用法一 的.then()调用),也就是成功回调函数与失败回调函数。
myPromise.prototype.then = function(fulfilled, rejected){
}
然后我们来继续完善 myPromise() 这个构造函数,前面我们说过Promise对象有三种状态,通过执行不同的回调(resolve()与reject())来改变这个Promise的状态。
function myPromise(constructor) {
let _this = this; // 保留原来this, 防止指向不明
_this.status = 'pending'; // 初始状态
function resolve() {
_this.status = 'fulfilled'; // 成功状态
}
function reject() {
_this.status = 'rejected'; // 失败状态
}
constructor(resolve, reject);
}
当然,我们在上面那个举例中就说明了,一旦状态改变了就不能再改变了,所以我们还需要在成功与失败回调中做一些判断,保证它们只做一次。
function myPromise(constructor) {
let _this = this; // 保留原来this, 防止指向不明
_this.status = 'pending'; // 初始状态
function resolve() {
if(_this.status === 'pending') {
_this.status = 'fulfilled'; // 成功状态
}
}
function reject() {
if(_this.status === 'pending') {
_this.status = 'rejected'; // 失败状态
}
}
constructor(resolve, reject);
}
我们再看到.then()方法中是能接收到结果的,而我们的结果是在回调(resolve('我是异步返回的成功数据');)中传递进去的,所以我们要在成功与失败的回调中把结果也给保存一下,保证在调用.then()方法的时候有结果返回。
function myPromise(constructor) {
let _this = this; // 保留原来this, 防止指向不明
_this.status = 'pending'; // 初始状态
_this.result = undefined; // 保存成功的结果
_this.reason = undefined; // 保存失败的结果
function resolve(result) {
if(_this.status === 'pending') {
_this.status = 'fulfilled'; // 成功状态
_this.result = result;
}
}
function reject(reason) {
if(_this.status === 'pending') {
_this.status = 'rejected'; // 失败状态
_this.reason = reason;
}
}
constructor(resolve, reject);
}
保存了结果, myPromise() 构造方法做的事就差不多完了,剩下.then()方法,它需要做的只有一件事情,合理的返回对应的结果。
myPromise.prototype.then = function(fulfilled, rejected) {
let _this = this; // 该this与上方提的this是一致的,不懂的小伙伴要去看看原型链相关的内容哦。
switch(_this.status) {
case 'fulfilled':
fulfilled(_this.result);
break;
case 'rejected':
rejected(_this.reason);
break;
}
}
到此为止,Promise对象简单的结构基本就实现了,我们简单测试一下。
let p = new myPromise((resolve, reject) => {
if(true) { // 成功
resolve('成功数据');
}else { // 失败
reject('失败数据');
}
console.log('我先输入'); // 构造函数立刻执行
})
p.then(res => {
console.log(res); // 成功数据
}, err => {
console.log(err); //失败数据
})
这就完了吗? 当然没有,这只是同步处理的实现。
异步手写版
首先我们知道JS是个单线程,执行顺序是从上到下顺序执行,如果遇到异步任务就会先丢到一边继续往下执行,之后再回头,异步任务又可以分为宏任务与微任务,具体是个什么样子的概念就不展开说了(点它点它),反正记住如果是异步操作,那异步下面的代码肯定比它先执行,这要注意注意!!!
回到正题,看我们上面的 基本用法 里面都是含着一个异步操作,.then()方法调用的时候,实际我们还并未调用回调函数(resolve()与reject()都处于异步操作中,不会先执行)去改变Promise对象的状态,所以Promise对象的状态还是pending状态,此时我们并不知道执行.then()方法中的成功回调函数还是失败回调函数,也没有相关的结果可以返回。所以我需要先将成功回调函数还是失败回调函数保存起来,等待合适的时机再执行调用。
在 myPromise() 构造函数上创建个保存回调函数的地方,需要注意Promise对象支持链式调用,所以可能会连续调用多个.then()方法,所以可能会有多组成功回调函数与失败回调函数。
function myPromise(constructor) {
let _this = this; // 保留原来this, 防止指向不明
_this.status = 'pending'; // 初始状态
_this.result = undefined; // 保存成功的结果
_this.reason = undefined; // 保存失败的结果
_this.fulfilledCallbacks = []; // 存放成功的回调
_this.rejectedCallbacks = []; // 存放失败的回调
function resolve(result) {
if(_this.status === 'pending') {
_this.status = 'fulfilled'; // 成功状态
_this.result = result;
_this.fulfilledCallbacks.forEach(fulfilledCb => {
fulfilledCb();
});
}
}
function reject(reason) {
if(_this.status === 'pending') {
_this.status = 'rejected'; // 失败状态
_this.reason = reason;
_this.rejectedCallbacks.forEach(rejectedCb => {
rejectedCb();
});
}
}
constructor(resolve, reject);
}
.then()将成功回调函数与失败回调函数保存起来等待合适的时机执行调用。
myPromise.prototype.then = function(fulfilled, rejected) {
let _this = this; // 该this与上方提的this是一致的,不懂的小伙伴要去看看原型链相关的内容哦。
switch(_this.status) {
case 'fulfilled':
fulfilled(_this.result);
break;
case 'rejected':
rejected(_this.reason);
break;
case 'pending':
_this.fulfilledCallbacks.push(() => {
fulfilled(_this.result);
});
_this.rejectedCallbacks.push(() => {
rejected(_this.reason);
});
break;
}
}
测试代码(也就差不多是上面 基本用法 的代码了)
let p = new myPromise((resolve, reject) => {
setTimeout(() => { // 异步操作
if(false) { // 成功
resolve('我是异步返回的成功数据');
}else { // 失败
reject('我是异步返回的失败数据');
}
}, 1000)
console.log('我先输入'); // 构造函数立刻执行
})
p.then(res => {
console.log(res); // 我是异步返回的成功数据
}, err => {
console.log(err); //我是异步返回的失败数据
})
这就完了吗? 怎么问,肯定又还没有咯。
我们让Promise对象抛出一个错误看看。
let p = new myPromise((resolve, reject) => {
throw new Error('出错了')
})
p.then(res => {
console.log('res:' + res); // 我是异步返回的成功数据
}, err => {
console.log('err:' + err); //我是异步返回的失败数据
})
这种错误好像没有进入我们预想的失败回调函数一样,我们预想应该这种错误也要进入失败回调函数才是正常。
解决这种错误。
try{
constructor(resolve, reject);
}catch(e) {
reject(e);
}
这就好了,很完美,不错了,你已经很棒了。
完整源码
function myPromise(constructor) {
let _this = this; // 保留原来this, 防止指向不明
_this.status = 'pending'; // 初始状态
_this.result = undefined; // 保存成功的结果
_this.reason = undefined; // 保存失败的结果
_this.fulfilledCallbacks = []; // 存放成功的回调
_this.rejectedCallbacks = []; // 存放失败的回调
function resolve(result) {
if(_this.status === 'pending') {
_this.status = 'fulfilled'; // 成功状态
_this.result = result;
_this.fulfilledCallbacks.forEach(fulfilledCb => {
fulfilledCb();
});
}
}
function reject(reason) {
if(_this.status === 'pending') {
_this.status = 'rejected'; // 失败状态
_this.reason = reason;
_this.rejectedCallbacks.forEach(rejectedCb => {
rejectedCb();
});
}
}
constructor(resolve, reject);
}
myPromise.prototype.then = function(fulfilled, rejected) {
let _this = this; // 该this与上方提的this是一致的,不懂的小伙伴要去看看原型链相关的内容哦。
switch(_this.status) {
case 'fulfilled':
fulfilled(_this.result);
break;
case 'rejected':
rejected(_this.reason);
break;
case 'pending':
_this.fulfilledCallbacks.push(() => {
fulfilled(_this.result);
});
_this.rejectedCallbacks.push(() => {
rejected(_this.reason);
});
break;
}
}
(嘿嘿,其实,还有的一点内容的,我们上面不是说过.then()支持链式调用吗?呃......就留个悬念吧?如果后面有人要看再补上吧,哈哈,拜拜~)