文章目录
如果你想更加深入的了解Promise的使用以及它的内部机制,那不妨跟着我来手写一个Promise吧。
一.初始化Promise
首先我们应该清楚Promise的基本结构,Promise的原型方法以及函数对象本身的方法。
then
与catch
是我们Promise构造函数的方法,all
、race
、resolve
、reject
是函数对象上的方法,所以我们写列出以下的Promise基本架构,再去逐一攻破每个难关。
代码如下:
//状态
const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'
/* 构建Promise的结构 */
/*Promise的构造函数*/
function MyPromise(excuter) {}
/*
Promise原型对象上的then()
指定成功和失败的回调函数
*/
MyPromise.prototype.then = function (onResolve, onReject) {}
/*
Promise原型对象上的catch()
指定失败的回调函数
*/
MyPromise.prototype.catch = function (onReject) {}
/*
Promise函数对象的resolve方法
返回一个成功的promise
*/
MyPromise.resolve = function (value) {}
/*
Promise函数对象的reject方法
返回一个失败的promise
*/
MyPromise.reject = function (reason) {}
/*
Promise函数对象的all方法
返回一个promise,只有当所有promise成功时才成功,否则只要有一个失败就失败
*/
MyPromise.all = function (promises) {}
/*
Promise函数对象的race方法
返回一个promise,结果返回第一个完成的promise
*/
MyPromise.race = function (promises) {}
二.构造函数实现
完成构造函数的第一步就是我们要清楚Promise内部需要维护哪些状态,比如我们当前的状态status、以及我们结果数据、还有就是我们内部需要维护一个队列用于存储我们当前Promise的then里面的两个回调函数。
存储回调队列的原因
我们为什么要维护一个队列来进行存储回调函数?因为当我们的Promise的then的回调函数是分两种情况的,我们都知道是resolve或者reject触发我们内部的回调函数。
- 但是如果我们这两个操作放在耗时操作中时,那么我们就需要将回调函数存储起来,等到我们耗时操作到了执行栈中再去执行resolve或者reject,在这里面循环遍历我们回调函数。
- 如果我们异步的操作,直接Promise内时同步执行的,那么我们就不需要将其回调函数放入队列中了,我们就可以直接处理当前的回调函数了
resolve与reject函数
这两个函数是在Promise的函数体内的回调函数中的,用于将我们的值给then
中的回调函数
其次的话在这两个函数中我们应该做的一些操作就包括,改变当前状态,为data赋值,如果我们队列中有回调函数就直接执行(间接说明我们的操作是放在异步操作中的,如果是同步是不会执行这一步操作的)
实现过程:(也可以使用ES6的class来处理)
function MyPromise(excuter) {
const self = this//防止this的隐式丢失
self.status = PENDING//给Promise对象指定状态属性,初始值为pending
self.data = null;//给Promise对象指定一个用于存储结果数据的属性
self.callbacks = []//给个元素的结构:{onResolved(){},onRejected(){}}
function resolve(value) {
// 这是防止我们多次进行resolve()或者reject()操作
if (self.status !== PENDING) {
return
}
//将状态给位resolved
self.status = RESOLVED
// 保存value 数据
self.data = value
// 调用resolve已经指定过了回调函数。(resolve()在异步操作时)我们这时异步执行回调函数(放入任务队列)
if (self.callbacks.length > 0) {
// 这里就是我们then里面的回调函数(一定要异步执行,并且放入微任务中,这里我们暂且放在宏任务的setTimeout中)
setTimeout(() => {
self.callbacks.forEach(obj => {
obj.onResolved(value)
});
});
}
}
function reject(reason) {
if (self.status !== PENDING) {
return
}
self.status = REJECTED
self.data = reason
if (self.callbacks.length > 0) {
setTimeout(() => {
self.callbacks.forEach((obj) => {
obj.onRejected(reason)
})
});
}
}
// 进入后立即执行同步执行我们的excuter函数
try {
excuter(resolve, reject)
} catch (error) {
reject(error)
}
}
三.then函数的实现
首先我们来想一下then函数的使用,它是Promise的一个原型方法,并且返回Promise对象用于链式调用,有两个参数作为回调函数,分别是resolve过来的成功的回调与reject过来的失败的回调。
两种情况
其实我们上面也说过了,then处理回调是有两种情况的,分别是立即处理回调函数还是将回调函数放入队列。所以我们就可以根据当前的status来判断,如果当前的status是pending
时说明我们还没有调用resolve
或者reject
,那直接将回调函数的执行加入队列中。
如果当前状态是RESOLVED
或者REJECTED
,我们直接将其放入事件队列中(按理说我们应该将其放入微任务队列,但是我们这里直接将其放入定时器中,可以说明问题就好)
处理回调函数内部逻辑
首先第一步,执行回调函数并且将当前状态传出去让我们从外面的回调函数就可以拿到value或者reason。
执行完以后,我们需要返回一个Promise类型用来链式调用,而回调函数的返回值大体上分为三个情况:
1.当我们当前程序出现异常时reject(使用try/catch)
2.当我们当前回调函数执行返回的的类型不是Promise类型时直接resolve这个返回值(包括无返回值的undefined)
3.当我们当前回调函数执行返回的的类型是Promise类型,在这个Promise的then里面处理我们应该resolve还是reject。
处理异常穿透
异常穿透就是当我们有连续几个then时,但是我们第一个reject时发现第二个处理失败的回调函数没有设置,所以我们就需要响应的处理,就是在开始时判断我们的回调函数是否有值,有值则不懂,无值则将结果throw
给下一层,同理我们也可以处理成功的回调。
代码实现:
MyPromise.prototype.then = function (onResolved, onRejected) {
const self = this//防止this的隐式丢失
// 实现异常穿透(就是当我们没有传相对应的值时我们呢重新创建一个函数返回我们当前的结果集)
onRejected = typeof onRejected === "function" ? onRejected : () => { throw self.data }
// 实现正常穿透
onResolved = typeof onResolved === "function" ? onResolved : () => self.data
return new MyPromise((resolve, reject) => {
// 实现handle处理逻辑
function handle(callback) {
// 返回Promise的实例:
/*
1.当我们当前程序出现异常时reject
2.当我们当前的类型不是Promise类型时返回我们传过来的值
3.当我们当前的类型时Promise类型,返回上一层Promise的值
*/
try {
let tempresolve = callback(self.data)
if (tempresolve instanceof MyPromise) {
// 第三中
tempresolve.then((res) => {
resolve(res)
}, (reason) => {
reject(reason)
})
// 可以替换为
// tempresolve.then(resolve, reject)
} else {
// 第二种
resolve(tempresolve)
}
} catch (error) {
// 第一种
reject(error)
}
}
switch (this.status) {
case PENDING:
self.callbacks.push({
onResolved() { handle(onResolved) },
onRejected() { handle(onRejected) }
})
break;
case RESOLVED:
setTimeout(() => {
handle(onResolved)
});
break;
case REJECTED:
setTimeout(() => {
handle(onRejected)
});
break;
default:
break;
}
})
}
四. catch的实现
实现了then,那么catch相对来说就好实现了,我们只需要调用then再将then的第一个参数设为空就可以了
实现:
MyPromise.prototype.catch = function (onReject) {
// catch统一处理
return this.then(null, onReject)
}
五. Promise.reject的实现
这是上就是一个语法糖,用来返回一个失败的Promise对象。
MyPromise.reject = function (reason) {
return new MyPromise((resolve, reject) => {
reject(reason)
})
}
六. Promise.resolve的实现
这也是Promise的一个语法糖,不同的是,它的内部可以继续跟一个Promise对象
- 如果里面是一个Promise的话我们就要根据这个Promise的返回来确定我们最终的返回值。
- 如果是正常的值我们直接返回成功的状态即可
MyPromise.resolve = function (value) {
return new MyPromise((resolve, reject) => {
// 如果当前的参数是Promise对象需要进行下一步判断
if (value instanceof MyPromise) {
value.then(
(value) => {
resolve(value)
},
(reason) => {
reject(reason)
})
} else {//如果当前参数是一个普通值直接resolve
resolve(value)
}
})
}
七. Promise.all方法
这个方法接收一个包含任意类型的数组,一般我们就将Promise放入。它会等到我们所有的Promise执行结束再按照原先的顺序去返回结果集,但是如果我们一旦遇到一个失败的状态就直接返回失败。
所以我们需要记录我们当前成功的结果集吗,在设置一个计数器,用来统计我们成功的次数。
根据上面的分析,我们在遍历的时候应该注意以下几点:
- 不能直接遍历我们数组参数,而是将其包裹在Promise.resolve中,因为其返回Promise对象(解决数组是任意类型的问题)
- 不能使用push将我们当前的成功结果放入成功结果集中,而是使用数组下标加入(解决Promise按照顺序放入成功数组中)
- 成功的条件是我们的计数器与当前的成功集长度相等时,也就是与我们的参数长度相等时。说明全部成功
代码实现:
MyPromise.all = function (promises) {
return new MyPromise((resolve, reject) => {
let resolveArr = new Array(promises.length);
let addResolve = 0
promises.forEach((pro, index) => {
MyPromise.resolve(pro).then(
(values) => {
// 这里我们应该将所有成功的promise放入一个数组并且顺序按照promise的顺序。
// 思考(为什么不适用push)
resolveArr[index] = values;
addResolve++;
// 如果当前的计数器等于我们定义的数组的长度的话我们就直接resolve当前数组。
if (addResolve === resolveArr.length) {
resolve(resolveArr)
}
},
//如果我们当前只要有reject就直接返回即可
(reason) => {
reject(reason)
}
)
})
})
}
八. Promise.race方法
这个方法是哪个先完成就直接返回哪个promise,你管你是成功还是失败时返回一个Promise。
实现:
MyPromise.race = function (promises) {
return new Promise((resolve, reject) => {
promises.forEach((pro) => {
MyPromise.resolve(pro).then(resolve, reject)
})
})
}
源码附上:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
/* 构建Promise的结构 */
/*Promise的构造函数*/
const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'
function MyPromise(excuter) {
const self = this//防止this的隐式丢失
self.status = PENDING//给Promise对象指定状态属性,初始值为pending
self.data = null;//给Promise对象指定一个用于存储结果数据的属性
self.callbacks = []//给个元素的结构:{onResolved(){},onRejected(){}}
function resolve(value) {
// 这是防止我们多次进行resolve()或者reject()操作
if (self.status !== PENDING) {
return
}
//将状态给位resolved
self.status = RESOLVED
// 保存value 数据
self.data = value
// 调用resolve已经指定过了回调函数。(resolve()在异步操作时)我们这时异步执行回调函数(放入任务队列)
if (self.callbacks.length > 0) {
// 这里就是我们then里面的回调函数(一定要异步执行,并且放入微任务中,这里我们暂且放在宏任务的setTimeout中)
setTimeout(() => {
self.callbacks.forEach(obj => {
obj.onResolved(value)
});
});
}
}
function reject(reason) {
if (self.status !== PENDING) {
return
}
self.status = REJECTED
self.data = reason
if (self.callbacks.length > 0) {
setTimeout(() => {
self.callbacks.forEach((obj) => {
obj.onRejected(reason)
})
});
}
}
// 进入后立即执行同步执行我们的excuter函数
try {
excuter(resolve, reject)
} catch (error) {
reject(error)
}
}
/*
Promise原型对象上的then()
指定成功和失败的回调函数
*/
MyPromise.prototype.then = function (onResolved, onRejected) {
const self = this//防止this的隐式丢失
// 实现异常穿透(就是当我们没有传相对应的值时我们呢重新创建一个函数返回我们当前的结果集)
onRejected = typeof onRejected === "function" ? onRejected : () => { throw self.data }
// 实现正常穿透
onResolved = typeof onResolved === "function" ? onResolved : () => self.data
return new MyPromise((resolve, reject) => {
// 实现handle处理逻辑
function handle(callback) {
// 返回Promise的实例:
/*
1.当我们当前程序出现异常时reject
2.当我们当前的类型不是Promise类型时返回我们传过来的值
3.当我们当前的类型时Promise类型,返回上一层Promise的值
*/
try {
let tempresolve = callback(self.data)
if (tempresolve instanceof MyPromise) {
// 第三中
tempresolve.then((res) => {
resolve(res)
}, (reason) => {
reject(reason)
})
// 可以替换为
// tempresolve.then(resolve, reject)
} else {
// 第二种
resolve(tempresolve)
}
} catch (error) {
// 第一种
reject(error)
}
}
switch (this.status) {
case PENDING:
self.callbacks.push({
onResolved() { handle(onResolved) },
onRejected() { handle(onRejected) }
})
break;
case RESOLVED:
setTimeout(() => {
handle(onResolved)
});
break;
case REJECTED:
setTimeout(() => {
handle(onRejected)
});
break;
default:
break;
}
})
}
/*
Promise原型对象上的catch()
指定失败的回调函数
*/
MyPromise.prototype.catch = function (onReject) {
// catch统一处理
return this.then(null, onReject)
}
/*
Promise函数对象的resolve方法(参数可以是一个Promise对象)
返回一个成功的promise
*/
MyPromise.resolve = function (value) {
return new MyPromise((resolve, reject) => {
// 如果当前的参数是Promise对象需要进行下一步判断
if (value instanceof MyPromise) {
value.then(
(value) => {
resolve(value)
},
(reason) => {
reject(reason)
})
} else {//如果当前参数是一个普通值直接resolve
resolve(value)
}
})
}
/*
Promise函数对象的reject方法(参数只能为一个普通类型的值)
返回一个失败的promise
*/
MyPromise.reject = function (reason) {
return new MyPromise((resolve, reject) => {
reject(reason)
})
}
/*
Promise函数对象的all方法
返回一个promise,只有当所有promise成功时才成功,否则只要有一个失败就失败(返回的数组的顺序与传入的promise对象的顺序保持一致)
*/
MyPromise.all = function (promises) {
return new MyPromise((resolve, reject) => {
let resolveArr = new Array(promises.length);
let addResolve = 0
promises.forEach((pro, index) => {
MyPromise.resolve(pro).then(
(values) => {
// 这里我们应该将所有成功的promise放入一个数组并且顺序按照promise的顺序。
// 思考(为什么不适用push)
resolveArr[index] = values;
addResolve++;
// 如果当前的计数器等于我们定义的数组的长度的话我们就直接resolve当前数组。
if (addResolve === resolveArr.length) {
resolve(resolveArr)
}
},
//如果我们当前只要有reject就直接返回即可
(reason) => {
reject(reason)
}
)
})
})
}
/*
Promise函数对象的race方法
返回一个promise,结果返回第一个完成的promise
*/
MyPromise.race = function (promises) {
return new Promise((resolve, reject) => {
promises.forEach((pro) => {
MyPromise.resolve(pro).then(resolve, reject)
})
})
}
// 测试------------------------------------------------------------------------
// let test = new MyPromise((resolve, reject) => {
// // setTimeout(() => {
// // reject(1);
// // });
// reject(1);
// // resolve(2)
// }).then((res) => {
// console.log('onResolved2()', res)
// }, (reason) => {
// console.log('onRejected2()', reason)
// return new MyPromise((res) => {
// res(123123)
// })
// }).then((res) => {
// console.log('onResolved2()', res)
// }, (reason) => {
// console.log('onRejected2()', reason)
// })
const p1 = new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(3000)
}, 3000);
})
const p2 = new MyPromise((resolve, reject) => {
setTimeout(() => {
reject(2000)
}, 2000);
})
const p3 =new MyPromise((resolve, reject) => {
setTimeout(() => {
resolve(4000)
}, 4000);
})
// p1.then((res) => {
// console.log(res)
// })
// p2.then((res) => {
// console.log(res)
// }, (rea) => {
// console.log(rea)
// })
// p3.catch((res) => {
// console.log(res)
// })
// PromiseAll的验证
// MyPromise.all([p1, p2, p3]).then(
// (value) => {
// console.log(value)
// }
// , (reason) => {
// console.log(reason)
// }
// )
// PromiseRace的验证
MyPromise.race([p1, p2, p3]).then((values) => {
console.log(values)
}, (reason) => {
console.log(reason)
})
// 3,2,4,1
// 2,5,3,4,1
// 3,7,4,1,2,5
// 1,7,2,3,
</script>
</html>