我们现在明白了如何使用Promise
了,所以我们需要来根据需求来思考下如何实现一个Promise
Promise
是一个对象,因为我们每次是通过new
来实例一个Promise
Promise
是对象,则代表存在Promise
中有一个构造函数,这个构造函数接受一个参数:函数参数resolve
和rejected
,分别代表这成功回调和失败回调rejected
或者resolve
其中一个就可以使得Promise
改变状态,通过阅读文档了解到Promise
一共有3种状态:pending
、fail
和fullfilled
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
很多眼尖的同学发现这里的代码是有问题的,因为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)})
首先我们创建了一个 Promise
实例,这里叫做promise1
;接着会运行fn1(resolve);
但是 fn1 中有一个setTimeout
函数,于是就会先跳过这一部分,运行后面的第一个then
方法;
then
返回一个新的对象promise2, promise2
对象的resolve
方法和then
方法的中回调函数 fn2
都被封装在callback
中, 然后 callback
被添加到 _fnResolve
数组中。同理,fn3
也保存到_fnResolve
到此两个 then
运行结束。 setTimeout
中的延迟时间一到,就会调用 promise1
的 resolve
方法。
resolve
方法的执行,会调用 _resolves
数组中的回调,之前我们添加的 fn2
方法就会被执行; 也就是promsie2
的 resolve
方法,都被调用了。
以此类推,fn3
会和 promise3
的resolve
方法 一起执行,因为后面没有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实例,当然传入常量会自己解析成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 = 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
并且结合状态进行判断就可以完成了。