前言
大家好,我是青年野味,相信这么多年的开发过程中,大家都对Promise或多或少的了解,但是在面对一些面试题时时犹豫不决,原因终究还是不了解Promise的原理,今天带着大家用易理解的方式一起来实现一下,这样我自己也能得到,同时提升希望能帮助的到大家,如果大家有建议或者发现问题的都可以在下面评论中指出,我也会积极更正过来的
认识Promise
Promise 是异步编程的一种解决方案: 从语法上讲,promise是一个对象,从它可以获取异步操作的消息;从本意上讲,它是承诺,承诺它过一段时间会给你一个结果。 promise有三种状态:pending(等待态),fulfiled(成功态),rejected(失败态) ;状态一旦改变,就不会再变。创造promise实例后,它会立即执行。
我相信大家经常写这样的代码:
// 当参数a大于10且参数fn2是一个方法时 执行fn2
function fn1(a, fn2) {
if (a > 10 && typeof fn2 == 'function') {
fn2()
}
}
fn1(11, function() {
console.log('this is a callback')
})复制代码
一般来说我们会碰到的回调嵌套都不会很多,一般就一到两级,但是某些情况下,回调嵌套很多时,代码就会非常繁琐,会给我们的编程带来很多的麻烦,这种情况俗称——回调地狱。
这时候我们的promise就应运而生、粉墨登场了
promise是用来解决两个问题的:
- 回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象
- promise可以支持多个并发的请求,获取并发请求中的数据
- 这个promise可以解决异步的问题,本身不能说promise是异步的
Promise API
Promise
对象是一个构造函数,用来生成Promise实例的,自身拥有着我们平时用的all、resolve、reject等熟悉的方法,原型上有着then、catch等一样熟悉的的方法。
new Promise
Promise的构造函数接收一个参数:函数,并且这个函数需要传入两个参数:
-
resolve :异步操作执行成功后的回调函数
-
reject:异步操作执行失败后的回调函数
说走就走,让我们来new一个Promise实例吧
let p = new Promise((resolve, reject) => {
console.log("青年野味new了一个Promise")
resolve("我成功了")
//reject('我失败了');
});
//resolve时
console.log("p",p) //p Promise {<fulfilled>: '我成功了'}
//reject时
console.log("p",p) //p Promise {<rejected>: '我失败了'},控制台会抛出一个错误
//同时执行resolve和reject时
console.log("p",p) //p Promise {<fulfilled>: '我成功了'}
// throw时
let p2 = new Promise((resolve, reject) => { throw('我报错了') })
根据上面的情况我们总结一下:
-
创造promise实例后,它会立即执行。
-
当执行了
resolve
,Promise状态会变成fulfilled
,即成功状态 -
执行了
reject
,Promise状态会变成rejected
,即失败状态 -
当
resolve和reject
同时存在时,Promise只以第一次为准
,第一次成功就永久
为fulfilled
,第一次失败就永远状态为rejected
,状态一旦改变,就不会再变。 -
当Promise中有
throw
的话,就相当于执行了reject
那么我们就把这个四种情况一一实现了。
class yw_Promise {
constructor(executor) {
//默认状态为padding
this.state = "pending"
this.result = null
this.onResolvedCallbacks = []
this.onRejectedCallbacks = []
const resolve = (data) => {
if (this.state !== "pending") return
this.state = "fulfiled"
this.result = data
//当我们数组里边没有时就不用去执行
while (this.onResolvedCallbacks.length) {
this.onResolvedCallbacks.shift()(data)
}
}
const reject = (data) => {
if (this.state !== "pending") return
this.state = "rejected"
this.result = data
//当我们数组里边没有时就不用去执行
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(data)
}
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
}
const yw_p = new yw_Promise((resolve, reject) => {
// resolve("初次见面,成功了")
// reject("初次见面,失败了")
throw("初次见面,失败了")
})
console.log(yw_p) //yw_Promise {state: 'rejected', result: '初次见面,失败了'}
then
then
是为 Promise 实例添加状态改变时的回调函数。前面说过,then
方法的第一个参数是resolved
状态的回调函数,第二个参数是rejected
状态的回调函数,它们都是可选的。then
方法返回的是一个新的Promise
实例。
平时我们使用then是这样的:
//立即输出"OK"
let p = new Promise((resolve, reject) => {
resolve('OK');
}).then(value => console.log(value) , (err) => console.error(err) );;
// 1秒后输出 ERROR
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('ERROR')
}, 1000)
}).then(res => console.log(res), err => console.log(err))
// 链式调用 输出 20
let p3 = new Promise((resolve, reject) => {
resolve(10)
}).then(res => 2 * res, err => console.log(err))
.then(res => console.log(res), err => console.log(err))
我们来总结一下上面的知识点:
then
函数中有两个回调参数,一个是成功的回调,一个是失败的回调- 当Promise状态为
fulfilled
执行成功回调
,为rejected
执行失败回调
- 当
reject或者resolve
在定时器中时,则等定时器执行完才再执行then
then链式调用
时,下一次then执行时受上一次then返回值的影响
根据上面的知识点,我们一一来实现吧
class yw_Promise {
...
then(onResolved, onRejected){
if(this.state === "fulfiled"){
onResolved(this.value)
}
if(this.state === "rejected"){
onRejected(this.value)
}
}
}
以为就这样就完了吗?那就大错特错了,上面都没有考虑到定时器和链式调用
的情况呢,首先定时器情况是异步的,又因为我们需要等定时器执行完then方法才会执行
,我们可以来详细看下这个定时器时候Promise的状态。
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('OK');
}, 1000);
});
console.log("同步输出p",p) //同步输出p Promise {<pending>}
setTimeout(()=>{
console.log("异步输出p",p) //异步输出p Promise {<fulfilled>: 'OK'}
},2000)
当我们同步的时候发现Promise的状态还是pending
,到了2秒后我们再输出就变成了fulfilled
状态了。
那么问题就来我怎么知道它到底是什么时候变成了fulfilled状态
?其实很简单,看定时器首先是不是要跑完,跑完了是不是要定时器里边的resolve或者是reject
,这两个我们在constructor
中写的是同步的吧,这两个一执行我们是不是就可以把状态改成相应的状态了,那状态肯定就不是pending
,而是fulfilled或者rejected
由此then中判断如果Promise状态是pending
,那这时候我们可以把每次调用resolve的结果存入一个数组中,每次调用reject的结果存入一个数组。
到这里还是很好理解的,但是在后面这一步的时候就有点难懂了。假设我现在存了有两个数组分别是onResolvedCallbacks、onRejectedCallbacks
,我们的定时器也到点要跑完了,这时候要去执行一开始里边写的resolve('OK');
,那你是不是要把之前存进去的回调结果一一执行了,那要怎么执行呢,从后面开始执行还是从前面开始执行。正确答案是从数组的下标0
开始一一执行,因为我们要有先后顺序的排队依次出来。
根据上面说的我们来完善一下
class yw_Promise {
constructor(executor) {
//默认状态为padding
this.state = "padding"
this.result = undefined
//存放成功的回调
this.onResolvedCallbacks = []
//存放失败的回调
this.onRejectedCallbacks = []
const resolve = (data) => {
if (this.state !== "padding") return
this.state = "fulfiled"
this.result = data
//当我们数组里边没有时就不用去执行
while (this.onResolvedCallbacks.length) {
this.onRejectedCallbacks.shift()(data)
}
}
const reject = (data) => {
if (this.state !== "padding") return
this.state = "rejected"
this.result = data
//当我们数组里边没有时就不用去执行
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(data)
}
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onFulFilled, onRejected) {
if (this.state === "fulfiled") {
onResolved(this.result)
}
if (this.state === "rejected") {
onRejected(this.result)
}
// 当前既没有完成 也没有失败
if (this.state === 'pending') {
// 存放成功的回调
this.onResolvedCallbacks.push(() => {
onFulFilled(this.result);
});
// 存放失败的回调
this.onRejectedCallbacks.push(() => {
onRejected(this.result);
});
}
}
}
又以为这就完了,那你有错了,还有链式调用和定时器
的情况呢,一样举个简单的例子给大家做介绍
let p = new Promise((resolve, reject) => {
resolve('OK');
}).then(value => {
// console.log(value)
return new Promise((resolve, reject) => {
resolve("success");
});
},err=>console.error(err)).then(value => {
console.log(value);
},err=>console.error(err)).then(value => {
console.log(value);
},err=>console.error(err))
setTimeout(()=>{
console.log("p",p) //p Promise {<fulfilled>: undefined}
},2000)
console.log("p",p)
//p Promise {<pending>}
//这里为什么会输出状态pending呢,原因是then是微任务
根据上面的例子我们总结一下知识点:
-
then方法本身会返回一个新的Promise对象
-
如果返回值是Promise对象,返回值为成功,新promise就是成功,返回值为失败,新Promise就是失败
-
如果返回值非Promise对象,新promise对象就是成功,值为此返回值
根据上面的总结的知识点,完善一下then
class yw_Promise {
....
then(onFulFilled, onRejected) {
//防止值得穿透
onFulFilled = typeof onFulFilled === 'function' ? onFulFilled : y => y;
onRejected = typeof onRejected === 'function' ? onRejected : err => {
throw err;
}
//作为下一次then方法的promise
let promise2 = new yw_Promise((resolve, reject) => {
const resolvePromise = resultCallback => {
setTimeout(() => {
try {
const x = resultCallback(this.result)
if (x === promise2) {
//不能自己等待自己完成
return reject(new TypeError('循环引用'));
}
if (x instanceof yw_Promise) {
x.then(resolve, reject)
} else {
// 非Promise就直接成功
resolve(x)
}
} catch (err) {
// 处理报错
reject(err)
}
})
}
if (this.state === "fulfiled") {
resolvePromise(onFulFilled)
}
if (this.state === "rejected") {
resolvePromise(onRejected)
}
// 当前既没有完成 也没有失败
if (this.state === 'pending') {
// 存放成功的回调
this.onResolvedCallbacks.push(() =>
resolvePromise(onFulFilled)
);
// 存放失败的回调
this.onRejectedCallbacks.push(() =>
resolvePromise(onRejected)
);
}
})
return promise2
}
}
注意:因为实际上的then是个微任务,但这里手写的then方法实际上是一个异步的宏任务来的,因为本人自己无法实现真正意义上的微任务,就这样写了一个setTimeout来代替,如果各位大佬有什么好的建议的,带带弟弟。
以上then
方法就算是完成了,但实际上如果你自己所定义的Promise需要和别人的Promise进行交互时,上面的resolvePromise
就需要重写。方法中传入四个参数,分别是promise2,x,resolve,reject,promise2指的是上一次返回的promise,x表示运行promise返回的结果,resolve和reject是promise2的方法。则代码写为:
function resolvePromise(promise2, x, resolve, reject) {
//判断x是不是promise
//规范中规定:我们允许别人乱写,这个代码可以实现我们的promise和别人的promise 进行交互
if (promise2 === x) { //不能自己等待自己完成
return reject(new TypeError('循环引用了'));
};
// x是除了null以外的对象或者函数
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
let called; //防止成功后调用失败
try { //防止取then是出现异常 object.defineProperty
let then = x.then; //取x的then方法 {then:{}}
if (typeof then === 'function') { //如果then是函数就认为他是promise
//call第一个参数是this,后面的是成功的回调和失败的回调
then.call(x, y => { //如果Y是promise就继续递归promise
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject)
}, r => { //只要失败了就失败了
if (called) return;
called = true;
reject(r);
});
} else { //then是一个普通对象,就直接成功即可
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e)
}
} else { //x = 123 x就是一个普通值 作为下个then成功的参数
resolve(x)
}
}
//then中使用可以交互的resolvePromise方法
then(){
...
if (this.state === "fulfiled") {
let x = onFulFilled(this.result);
resolvePromise(promise2,x,resolve,reject)
}
if (this.state === "rejected") {
let x = onRejected(this.result);
resolvePromise(promise2,x,resolve,reject)
}
// 当前既没有完成 也没有失败
if (this.state === 'pending') {
// 存放成功的回调
this.onResolvedCallbacks.push(() => {
let x = onFulFilled(this.result);
return resolvePromise(promise2,x,resolve,reject)
});
// 存放失败的回调
this.onRejectedCallbacks.push(() => {
let x = onRejected(this.result);
return resolvePromise(promise2,x,resolve,reject)
});
}
}
catch
catch
其实本身就是一个then
方法来的,只不过只有reject回调参数
,resolve是undefined
//catch 方法
catch (onRejected) {
return this.then(undefined, onRejected);
}
resolve和reject
这里的resolve和reject并非是构造函数中接收的参数,但是跟接收参数中异步操作成功后的回调有着很相似的作用。
让我们来看一下,它们是怎么用的:
//第一种:
let p1 = Promise.resolve("青年野味来搞Primise了")
console.log("p1",p1) //p1 Promise {<fulfilled>: '青年野味来搞Primise了'}
//第二种:
let p2 = Promise.resolve(new Promise((resolve, reject) => {
// resolve('OK');
reject('Error');
}));
//reject时
console.log("p2",p2); //p2 Promise {<rejected>: 'Error'}
//resolve时
console.log("p2",p2); //p2 Promise {<fulfilled>: 'OK'}
resolve和reject
方法中,当我们使用时分两种情况:
-
如果传入的参数为非Promise类型的对象, 则返回的结果为相应状态的promise对象
(resolve为fulfilled状态,reject为rejected状态
) -
如果传入的参数为 Promise 对象, 则参数的结果决定了 resolve 的结果
那让我们一起实现第一种情况先
//class中的静态方法
static resolve(data) {
return new yw_Promise((resolve, reject) => resolve(data))
}
static reject(data) {
return new yw_Promise((resolve, reject) => reject(data))
}
第二种情况当我们参数是Promise呢,如果使用上面的,结果里面就会返回一个Promise,所以我们要将上面的代码完善一下。
//完整版
//class中的静态方法
static resolve(value) {
return new yw_Promise((resolve, reject) => {
if (value instanceof yw_Promise) {
value.then(v => {
resolve(v);
}, r => {
reject(r);
})
} else {
//状态设置为成功
resolve(value);
}
});
}
static reject(value) {
return new yw_Promise((resolve, reject) => {
if (value instanceof yw_Promise) {
value.then(v => {
resolve(v);
}, r => {
reject(r);
})
} else {
//状态设置为成失败
reject(value);
}
});
}
resolve和reject
方法也就算完成了,也是跟then方法一样,并不能跟别人的Promise做交互的。因为都用了instanceof
,举个例子: A instanceOf B,判断A是否经过B的原型链
。因为别人的Promise并不在我们自己写的Promise原型链的,所以判断结果都是false的,有兴趣的朋友可以自己研究一下。
all
想要有很多的朋友在开发中都会遇到,我有多个接口要发请求,但是我想依次按顺序进行发送请求,这个时候就可以用到all
方法
让我们来看看他的用法
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
return new Promise((resolve,reject)=>resolve(id));
});
Promise.all(promises).then(function (posts) {
console.log(posts) // [2, 3, 5, 7, 11, 13]
}).catch(function(reason){
// ...
});
const promises = [2, 3, 5, 7, 11, 13].map(function (id) {
if(id !== 3) return new Promise((resolve,reject)=>resolve(id));
return new Promise((resolve,reject)=>reject(id))
});
Promise.all(promises).then(function (posts) {
//...
}).catch(function(reason){
console.log(reason) //3
});
const promises = [2, 3, 5, 7, 11, 13]
Promise.all(promises).then(function (posts) {
console.log(posts) //[2, 3, 5, 7, 11, 13]
}).catch(function(reason){
console.log(reason) //此处并没有输出
});
根据上面的我们来总结一下知识点:
- 如果所有Promise都成功,则返回成功结果数组
- 如果有一个Promise失败,则返回这个失败结果
- 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
那就让我们来实现一下吧
//添加 all 方法
static all(promises) {
//返回结果为promise对象
return new yw_Promise((resolve, reject) => {
//声明变量
let count = 0;
let arr = [];
//遍历
for (let i = 0; i < promises.length; i++) {
if (promises[i] instanceof yw_Promise) {
promises[i].then(v => {
//得知对象的状态是成功
//每个promise对象 都成功
count++;
//将当前promise对象成功的结果 存入到数组中
arr[i] = v;
//判断
if (count === promises.length) {
//修改状态
resolve(arr);
}
}, r => {
reject(r);
});
}
}
});
}
race
- 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
- 哪个Promise最快得到结果,就返回那个结果,无论成功失败
//添加 race 方法
static race(promises) {
return new yw_Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
if (promises[i] instanceof yw_Promise) {
promises[i].then(v => {
//修改返回对象的状态为 『成功』
resolve(v);
}, r => {
//修改返回对象的状态为 『失败』
reject(r);
})
}
}
});
}
allSettled
- 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
- 把每一个Promise的结果,集合成数组,返回
static allSettled(promises) {
return new yw_Promise((resolve, reject) => {
//声明变量
let count = 0;
let arr = [];
const addData = (status, value, i) => {
arr[i] = {
status,
value
}
count++
if (count === promises.length) {
resolve(arr)
}
}
//遍历
for (let i = 0; i < promises.length; i++) {
if (promises[i] instanceof MyPromise) {
promises[i].then(v => {
addData('fulfilled', v, i)
}, r => {
addData('rejected', r, i)
});
} else {
addData('fulfilled', promises[i], i)
}
}
});
}
any
any与all相反
- 接收一个Promise数组,数组中如有非Promise项,则此项当做成功
- 如果有一个Promise成功,则返回这个成功结果
- 如果所有Promise都失败,则报错
static any(promises) {
return new Promise((resolve, reject) => {
let count = 0
for (let i = 0; i < promises.length; i++) {
promises[i].then(val => {
resolve(val)
}, err => {
count++
if (count === promises.length) {
reject(new AggregateError('All promises were rejected'))
}
})
}
})
}
自定义Promise完整代码,细品
class yw_Promise {
constructor(executor) {
//默认状态为padding
this.state = "pending"
this.result = null
this.onResolvedCallbacks = []
this.onRejectedCallbacks = []
const resolve = (data) => {
if (this.state !== "pending") return
this.state = "fulfiled"
this.result = data
//当我们数组里边没有时就不用去执行
while (this.onResolvedCallbacks.length) {
this.onResolvedCallbacks.shift()(data)
}
}
const reject = (data) => {
if (this.state !== "pending") return
this.state = "rejected"
this.result = data
//当我们数组里边没有时就不用去执行
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(data)
}
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
then(onFulFilled, onRejected) {
//防止值得穿透
onFulFilled = typeof onFulFilled === 'function' ? onFulFilled : y => y;
onRejected = typeof onRejected === 'function' ? onRejected : err => {
throw err;
}
//作为下一次then方法的promise
let promise2 = new yw_Promise((resolve, reject) => {
const resolvePromise = resultCallback => {
setTimeout(() => {
try {
const x = resultCallback(this.result)
if (x === promise2) {
//不能自己等待自己完成
return reject(new TypeError('循环引用了'));
}
if (x instanceof yw_Promise) {
x.then(resolve, reject)
} else {
// 非Promise就直接成功
resolve(x)
}
} catch (err) {
// 处理报错
reject(err)
}
})
}
if (this.state === "fulfiled") {
resolvePromise(onFulFilled)
}
if (this.state === "rejected") {
resolvePromise(onRejected)
}
// 当前既没有完成 也没有失败
if (this.state === 'pending') {
// 存放成功的回调
this.onResolvedCallbacks.push(() =>
resolvePromise(onFulFilled)
);
// 存放失败的回调
this.onRejectedCallbacks.push(() =>
resolvePromise(onRejected)
);
}
})
return promise2
}
//catch 方法
catch (onRejected) {
return this.then(undefined, onRejected);
}
//class中的静态方法
static resolve(value) {
return new yw_Promise((resolve, reject) => {
if (value instanceof yw_Promise) {
value.then(v => {
resolve(v);
}, r => {
reject(r);
})
} else {
//状态设置为成功
resolve(value);
}
});
}
static reject(value) {
return new yw_Promise((resolve, reject) => {
if (value instanceof yw_Promise) {
value.then(v => {
resolve(v);
}, r => {
reject(r);
})
} else {
//状态设置为成功
reject(value);
}
});
}
//添加 all 方法
static all(promises) {
//返回结果为promise对象
return new yw_Promise((resolve, reject) => {
//声明变量
let count = 0;
let arr = [];
//遍历
for (let i = 0; i < promises.length; i++) {
if (promises[i] instanceof MyPromise) {
promises[i].then(v => {
//得知对象的状态是成功
//每个promise对象 都成功
count++;
//将当前promise对象成功的结果 存入到数组中
arr[i] = v;
//判断
if (count === promises.length) {
//修改状态
resolve(arr);
}
}, r => {
reject(r);
});
}
}
});
}
//添加 race 方法
static race(promises) {
return new yw_Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
if (promises[i] instanceof MyPromise) {
promises[i].then(v => {
//修改返回对象的状态为 『成功』
resolve(v);
}, r => {
//修改返回对象的状态为 『失败』
reject(r);
})
}
}
});
}
static allSettled(promises) {
return new yw_Promise((resolve, reject) => {
//声明变量
let count = 0;
let arr = [];
const addData = (status, value, i) => {
arr[i] = {
status,
value
}
count++
if (count === promises.length) {
resolve(arr)
}
}
//遍历
for (let i = 0; i < promises.length; i++) {
if (promises[i] instanceof MyPromise) {
promises[i].then(v => {
addData('fulfilled', v, i)
}, r => {
addData('rejected', r, i)
});
} else {
addData('fulfilled', promises[i], i)
}
}
});
}
static any(promises) {
return new Promise((resolve, reject) => {
let count = 0
for (let i = 0; i < promises.length; i++) {
promises[i].then(val => {
resolve(val)
}, err => {
count++
if (count === promises.length) {
reject(new AggregateError('All promises were rejected'))
}
})
}
})
}
}
拓展
有想了解跟多Promise相关Api的可以去这里看
结语
过程总是令人烦恼的,但是结果总是出乎意外的令人的兴奋。希望这个可以帮助到大家,觉得不错的可以点赞加收藏,觉得其中写的有问题的也可以在下方评论区告诉我,让我们一直在进步的路上