JavaScript Promise使用与实现-----自己写一个Promise

梳理一下

我们现在明白了如何使用Promise了,所以我们需要来根据需求来思考下如何实现一个Promise

  1. 首先Promise是一个对象,因为我们每次是通过new来实例一个Promise
  2. Promise是对象,则代表存在Promise中有一个构造函数,这个构造函数接受一个参数:函数参数
  3. 这个函数参数已经有了具体规则,就是接收2个参数resolverejected,分别代表这成功回调和失败回调
  4. 只需要调用rejected或者resolve其中一个就可以使得Promise改变状态,通过阅读文档了解到Promise一共有3种状态:pendingfailfullfilled
  5. 能够链式调用,设置多个函数回调

promise的样子

function Promise (fn) {
	/* 这里没用使用ES6的CLASS */
	this._promise = this // 保存指针引用
	this._status = "PENDING"// 引入状态
	// 用于保存回调函数
	this._fnRes = []
	this._fnRej = []
	this._res = function (){
		// 成功回调
		this._status="FULLFILLED"
	}
	this._rej = function (){
		// 失败回调
		this._status="FAIL"
	}
	// 调用函数
	fn(_res,_rej)
}
Promise.prototype.then = function (_resCallback,_rejCallback) {
	// 链式回调
	return new Promise ((res,rej)=>{

	})
}
Promise.prototype.catch = function () {
	
}
// 原型方法还有....

Promise引入的状态控制,从而来判断异步函数的调用

这里我们简单来设计下,如果请求成功则调用resolve状态变为FULLFILLED,请求失败调用rejected状态变为FAIL
其实这个状态的设置就是为了链式调用和Promise里面嵌套Promise而存在的

Promise.prototype.then = function (_resCallback,_rejCallback) {
	// 链式回调
	// 来添加回调事件
	return new Promise ((res,rej)=>{
		if(this._status==="PENDING"){// 初始化状态
			this._fnRes.push(_resCallback)// 保存成功回调函数到栈
			this._fnRej = _rejCallback
		}  else if(this._statue === "FULLFILLED") {
			res()// 链式回调
		} else {
			rej()// 链式回调
		}

	})
}

有细心的同学发现了一个问题,如果按照这样写的话,在我们声明Promise实例的时候就已经调用的resolve,这样this._fnRes还是空的,其实我们只需要在这里利用下JS的异步事件处理机制就可以解决问题

	this._res = function (){
		// 成功回调
		setTimeout(()=>{		
			this._status="FULLFILLED"
		},0)
	}

利用setTimeout来使得我们在调用玩then才来调用resolve
这里有一篇文章解释了JS的宏任务和微任务的区别
异步事件,大家如果没能理解原因建议详细的学习下。

从最简单的开始

一开始学习Promise的确是有点难看懂,所以我们先从最简单的Promise入手,一点点丰富它的功能
我们先实现成功回调函数

function P(fn) {
	this.value = null // 保存返回值
	this.resolve = function (callback) {
		callback()
	}
	fn(this.resolve)
}

在这里插入图片描述

添加then
function P(fn) {
	this.value = null // 保存返回值
	// 保存回调函数
	this._fnResolve = []
	this.resolve = function () {
	  //  callback()
	  this._fnResolve.forEach((item)=>{
	  	item()
	  })
	}
	fn(this.resolve)
}
P.prototype.then = function (callback) {
	this._fnResolve.push(callback)
}

_fnResolve保存的是成功的回调函数
resolve就是规则中的传入函数的第一个参数,调用了就是代表成功回调的函数
then将设置的回调函数保存到_fnResolve

!出现问题,this指向的问题

很多眼尖的同学发现这里的代码是有问题的,因为setTimeout会改变函数的this指向,所以需要使用call或者apply来进行修改

function P(fn) {
	this.value = null // 保存返回值
	// 保存回调函数
	this._fnResolve = []
	this.resolve = function () {
	  //  callback()
	  this._fnResolve.forEach((item)=>{
	  	item()
	  })
	}
	fn.call(this,this.resolve)// 修改this
}
P.prototype.then = function (callback) {
	this._fnResolve.push(callback)
}

问题还没有解决,我们这里直接使用看看情况会如何?

function P(fn) {
	this.value = null // 保存返回值
	// 保存回调函数
	this._fnResolve = []
	this.resolve = function () {
	  //  callback()
	  this._fnResolve.forEach((item)=>{
	  	item()
	  })
	}
	fn.call(this,this.resolve)// 修改this
}
P.prototype.then = function (callback) {
	this._fnResolve.push(callback)
}


let testP = new P(function(resolve){
    console.log('异步',this)
    this.resolve = resolve
    setTimeout(()=>  {
        resolve()
    }, 2000);

}).then(function(){
    console.log('then')
})
console.log('start')

输出情况
在这里插入图片描述
在调用resolve无法正确获取到this,是因为setTimeout会改变作用域
但是按照正确用法 用户是不用关心this的指向问题 ,所以我们需要强化下fn调用的方法

function P(fn) {
	this.value = null // 保存返回值
	// 保存回调函数
	this._fnResolve = []
	this.resolve = function () {
	  //  callback()
	  this._fnResolve.forEach((item)=>{
	  	item()
	  })
	}
	fn.call(this,this.resolve.bind(this))// 强化this.reovle
}
P.prototype.then = function (callback) {
	this._fnResolve.push(callback.bind(this)// 强化_fnResolve
}


let testP = new P(function(resolve){
    console.log('异步',this)
    setTimeout(()=>  {
        resolve()
    }, 2000);

}).then(function(){
    console.log('then')
})
console.log('start')

在这里插入图片描述
能够正确输出then啦,这里只需要修改resolve传入的方式,使用bind方法锁死resolve

加入链式支持
function P(fn) {
	this.value = null // 保存返回值
	// 保存回调函数
	this._fnResolve = []
	this.resolve = function () {
	  this._fnResolve.forEach((item)=>{
	  	item()
	  })
	}
	fn.call(this,this.resolve.bind(this)) // 强化this.reovle
}
P.prototype.then = function (callback) {
	// 修改的地方
	return new P((res)=>{
		this._fnResolve.push(callback.bind(this))// 强化_fnResolve
		res() // 链式调用的关键
	})
}
resolve异步事件改变执行顺序
function P(fn) {
	this.value = null // 保存返回值
	// 保存回调函数
	this._fnResolve = []
	this.resolve = function () {
	// 修改的地方
		setTimeout(()=>{	  
			this._fnResolve.forEach((item)=>{
		  	item()
		 })},0)
	}
	fn.call(this,this.resolve.bind(this)) // 强化this.reovle
}
P.prototype.then = function (callback) {
	return new P((res)=>{
		this._fnResolve.push(callback.bind(this))// 强化_fnResolve
		res() // 链式调用的关键
	})
}
加上异步结果的传递
function P(fn) {
	this.value = null // 保存返回值
	// 保存回调函数
	this._fnResolve = []
	this.resolve = function () {
	// 修改的地方
		setTimeout(()=>{	  
			this._fnResolve.forEach((item)=>{
		  		this.value= item(this.value)
		 })},0)
	}
	fn.call(this,this.resolve.bind(this)) // 强化this.reovle
}
P.prototype.then = function (callback) {
	return new P((res)=>{
		this._fnResolve.push(callback.bind(this))// 强化_fnResolve
		res() // 链式调用的关键
	})
}
串行promise

如果只是返回new P是无法完成自己设置的Promise的返回值,只能对一个全新的Promise进行链式then,所以需要使用返回值来保存结果并且进行判断。

function P(fn) {
	this.value = null // 保存返回值
	// 保存回调函数
	this._fnResolve = []
	this.resolve = function () {
	// 修改的地方
		setTimeout(()=>{	  
			this._fnResolve.forEach((item)=>{
		  		this.value= item(this.value)
		 })},0)
	}
	fn.call(this,this.resolve.bind(this)) // 强化this.reovle
}
P.prototype.then = function (callback) {
	return new P((res)=>{
		function handle(value) {
			var ret = typeof callback == 'function' && callback(value) || value
			 if( ret && typeof ret ['then'] == 'function'){
                    ret.then(function(value){
                       res(value);
                    });
                } else {
                    res(ret);
                }
			
		}

		this._fnResolve.push(handle)


	})
}

var ret = typeOf callback == 'function' && callback(value) || value
这个大家不知道是什么,我们一点点分析这个值。
首先是判断then传入的是否为函数,如果是则直接调用获取返回值,如果没有返回值,则为本身,否则则为返回值
然后我们判断ret是否为P实例,判断是否存在then方法,如果返回值是一个P实例,则将这个then传入的回调传入这个返回值的P实例。
这里是有点复杂,不过也是Promise设计的巧妙之处了。
进行到这里,我们运行看看是否正常。

function P(fn) {
	this.value = null // 保存返回值
	// 保存回调函数
	this._fnResolve = []
	this.resolve = function () {
	  //  callback()
		setTimeout(() => {
			this._fnResolve.forEach((item)=>{
				this.value= item(this.value)
			})
		}, 0);
	}
	fn.call(this,this.resolve.bind(this)) // 强化this.reovle
}
P.prototype.then = function (callback) {
	return new P((res)=>{
		function handle(value) {
			var ret = typeof callback == 'function' && callback(value) || value
			 if( ret && typeof ret ['then'] == 'function'){
                    ret.then(function(value){
                       res.call(this,value);
                    });
                } else {
									res.call(this,ret);
                }
			
		}
		this._fnResolve.push(handle.bind(this))// 强化_fnResolve
		
	})

}


let testP = new P(function(resolve){
    console.log('异步')
    setTimeout(()=>  {
        resolve()
    }, 2000);

}).then(function(){
    console.log('then1')
}).then(function(){
	console.log('then2')
})
console.log('start')

在这里插入图片描述
从结果看,的确是按照我预期来进行输出的。

引入状态
function P(fn) {
	this.value = null // 保存返回值
	this._Status = 'P'// 使用缩写代替
	// 保存回调函数
	this._fnResolve = []
	this.resolve = function () {
	// 修改的地方
		setTimeout(()=>{	  
			this._Status = 'F'
			this._fnResolve.forEach((item)=>{
		  		this.value= item(this.value)
		 })},0)
	}
	fn.call(this,this.resolve.bind(this)) // 强化this.reovle
}
P.prototype.then = function (callback) {
	return new P((res)=>{
		function handle(value) {
			var ret = typeOf callback == 'function' && callback(value) || value
			 if( ret && typeof ret ['then'] == 'function'){
                    ret.then(function(value){
                       res(value);
                    });
                } else {
                    res(ret);
                }
			
		}
		if(this._Status === "P") {
			this._fnResolve.push(handle.bind(this))// 强化_fnResolve
		} else {
			handle.call(this,this.value)
		}



	})
}

resolve调用的时候,代表这已经初始化完成,改变状态为FULLFILLED
我们现在来测试下返回P是否能够正常运行

function P(fn) {
	this.value = null // 保存返回值
	this._Status = 'P'// 使用缩写代替
	// 保存回调函数
	this._fnResolve = []
	this.resolve = function () {
	  //  callback()
		setTimeout(() => {
			this._Status = 'F'
			this._fnResolve.forEach((item)=>{
				this.value= item(this.value)
			})
		}, 0);
	}
	fn.call(this,this.resolve.bind(this)) // 强化this.reovle
}
P.prototype.then = function (callback) {
	return new P((res)=>{
		function handle(value) {
			var ret = typeof callback == 'function' && callback(value) || value
			 if( ret && typeof ret ['then'] == 'function'){
                    ret.then(function(value){
                       res.call(this,value);
                    });
				} else {
					res.call(this,ret);
				}
			
		}
		if(this._Status === "P") {
			this._fnResolve.push(handle.bind(this))// 强化_fnResolve
		} else {
			handle.call(this,this.value)
		}


		
	})

}


let testP = new P(function(resolve){
    console.log('异步')
    setTimeout(()=>  {
        resolve()
    }, 2000);

}).then(function(){
		console.log('then1')
		return new P((resolve)=>{
			console.log('异步2')
			setTimeout(()=>  {
					resolve()
			}, 2000);
		})
}).then(function(){
	console.log('then2')
})
console.log('start')

在这里插入图片描述
的确符合我们预期情况,是因为引入了状态控制,如果直接在then自己返回一个新的P实例的时候,会进行判断返回值是否为P和并且使得状态变化为PENDING


有些人在这里可能会有点犯晕,有必要对执行过程分析一下,具体参看以下代码:
new P(fn1).then(fn2).then(fn3)})

  1. 首先我们创建了一个 Promise实例,这里叫做promise1;接着会运行fn1(resolve);

  2. 但是 fn1 中有一个setTimeout 函数,于是就会先跳过这一部分,运行后面的第一个then 方法;

  3. then 返回一个新的对象promise2, promise2对象的resolve方法和then 方法的中回调函数 fn2都被封装在callback中, 然后 callback被添加到 _fnResolve数组中。同理,fn3也保存到_fnResolve
    在这里插入图片描述

  4. 到此两个 then运行结束。 setTimeout中的延迟时间一到,就会调用 promise1resolve方法。

  5. resolve方法的执行,会调用 _resolves数组中的回调,之前我们添加的 fn2 方法就会被执行; 也就是promsie2resolve方法,都被调用了。

  6. 以此类推,fn3 会和 promise3resolve 方法 一起执行,因为后面没有then方法了,_resolves 数组是空的 。至此所有回调执行结束
    在这里插入图片描述
    用一张流程图可以非常清晰看清楚过程
    在这里插入图片描述

添加失败处理
function P(fn) {
	this.value = null // 保存返回值
	this._reason = null // 保存是失败原因
	this._Status = 'P'// 使用缩写代替
	// 保存回调函数
	this._fnResolve = []
	this._fnRejected= []
	this.resolve = function () {
	  //  callback()
		setTimeout(() => {
			this._Status = 'F'
			this._fnResolve.forEach((item)=>{
				this.value= item(this.value)
			})
		}, 0);
	}
	this.reject = function () {
		setTimeout(()=>{
			this._Status = 'FAIL'
			this._fnRejected.forEach(function (callback) {
								this._reason = callback(this._reason)
      })
		},0)
	}
	fn.call(this,this.resolve.bind(this),	this.reject.bind(this)) // 强化this.reovle
}
P.prototype.then = function (callback,onReject) {
	return new P((res,rej)=>{
		function handle(value) {
			var ret = typeof callback == 'function' && callback(value) || value
			 if( ret && typeof ret ['then'] == 'function'){
								ret.then(function(value){
										res.call(this,value);
								},function(error){
										rej.call(this,error)
								});
				} else {
					res.call(this,ret);
				}
			
		}
		function errback(reason){
		
			reason =typeof onReject =='function'&& onReject(reason) || reason;
			rej.call(this,reason);
		}
		if(this._Status === "P") {
			this._fnResolve.push(handle.bind(this))// 强化_fnResolve
			this._fnRejected.push(errback.bind(this))
		} else if(this._Status === "F"){
			handle.call(this,this.value,this._reason)
		} else {
			errback.call(this,this.value,this._reason)
		}
	})

}


var testP = new P(function(resolve,rej){
    console.log('异步')
    setTimeout(()=>  {
        resolve()
    }, 2000);

}).then(function(){
		console.log('then1')
		return new P((resolve,rej)=>{
			console.log('异步2')
			setTimeout(()=>  {
				rej()
			}, 2000);
		})
},function(){
	console.log('rej1')
}).then(function(){
	console.log('then2')
},function(){
	console.log('rej2')
}).then(function(){
	console.log('then3')
},function(){
	console.log('rej3')
})
console.log('start')

添加失败方法很简单,因为和成功是相似的,区别就是状态。
在这里插入图片描述
这里会执行2次rejected是因为这里把后续的rej全部输出在这里插入图片描述
如果想要promise遇到错误就停,可以在这里直接输出第一个的rej的函数就可以了。

promise.all

这个方法的唯一一个参数就是一个数组,数组里面包含的是promise实例,当然传入常量会自己解析成promise.resolve(value)
返回值:一个promise,如果数组所有的promise都成功就执行resolve,其中一个失败,就执行reject

  • 使用方法
let pro = new Promise((res,resj)=>{
	setTimeout(()=>{
		res('pro')
	},3000)
})
let pro2 = new Promise((res,resj)=>{
	setTimeout(()=>{
		res('pro2')
	},1000)
})
Promise.all([pro,pro2]).then((value)=>{
  console.log(value)// [ 'pro', 'pro2' ]
})

输出[ 'pro', 'pro2' ],很明显与它传入的数组顺序是相关的。并且最后会等待所有异步执行完才会执行输出。

let pro = new Promise((res,resj)=>{
	setTimeout(()=>{
		res('pro')
	},3000)
})
let pro2 = new Promise((res,resj)=>{
	setTimeout(()=>{
		resj('error')
	},1000)
})
Promise.all([pro,pro2]).then((value)=>{
  console.log(value)
}).catch(item=>console.log(item))

输出error,只要有一个执行错误,就全部终止,输出错误信息。

自己写一个promise.all
_promise.all = function(arr) {
	return new Promise((re,rj)=>{
		// 判断arr是否为数组
		if(!Array.isArray(arr)) {
	      return rej('arris not array')
	    }
	    var len = arg.length// 数组长度
	    var resolvedCounter = 0;// 记录处理的数量
	    var result = []// 保存结果
	    for(let i=0;i<len;i++) {
	       Promise.resolve(arg[i]).then((data)=>{// 利用resolve来完成一个promise 的回调
	        resolvedCounter++
	        result.push(data)// 成功结果保存
	        if(resolvedCounter == len) return resolve(result)// 如果全部处理完 就返回所有结果
	      }).catch((data)=>{
	        return  rej(data)// 如果有一个出现错误 直接返回失败结果
	      })
    }
	 
	})
}

拓展

除了使用settimout来使得promise的任务顺序改变之外,还可以使用generator来实现,这里就不说的那么详细。有兴趣可以自己去尝试下。
关于promise的封装的方法只要利用好返回promise并且结合状态进行判断就可以完成了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值