promise对象的理解
参考:
https://juejin.cn/post/6844903629187448845
https://juejin.cn/post/6844904088963022856
https://www.cnblogs.com/mfyngu/p/13880867.html
Promise对象
Promise是异步编程的一种解决方法。是一个构造函数,用来生成Promise实例。这个构造函数里有两个参数,分别是:resolve(成功之后的回调函数)、reject(失败之后的回调函数)。
promise实例生成之后,可以用then的方法分别指定resolved状态和rejected状态的回调函数,then方法接受两个回调函数作为参数。第一个回到函数是Promise对象的状态变为resolved时调用,第二个回调函数是Promise对象的状态变为rejected时调用。两个函数都是可选的,不一定提供。*通过。then指定回调函数的时候,成功的回调函数必须传,失败的回调函数可以省略(then方法接受的两个回调函数被调用的时机通过Promise对象的状态决定)
举个🌰:
let p = new Promise(function(resolve, reject){ //做一些异步操作 setTimeout(function(){ console.log('执行完成Promise'); resolve('要返回的数据可以任何数据 例如接口返回数据'); }, 2000); }); console.log('中间'); p.then(res => { console.log('then成功的回调是:', res) }) console.log('结尾')
打印输出:
中间
结尾
执行完成Promise
// 两秒后输出
then成功的回调是:要返回的数据可以任何数据 例如接口返回数据
上面的例子说明了promise可以很好的处理异步操作。因为promise的then方法,是在 resolve函数执行之后才调用的。
promise还有哪些实用呢?
首先理解一下promise
1、当promise的转态是pending的时候,可能会转化到fulfilled或者是rejected转态
2、当promise状态是fulfilled的时候,不能转化为其他的转态,必须返回一个value,并且这个value保持不变。
3、当promise的状态是rejected的时候,也不能转化为其他的状态,必须返回一个失败的原因。
因为promise,所以叫promise。
promise是用来解决回调地狱的问题的。可以链式调用,可以嵌套调用。
1、promise是一个构造函数,用来生成Promise实例。支持异步。这个构造函数里接受一个函数作为参数,该函数有两个参数,分别是:resolve(异步操作成功之后的回调函数)、reject(异步操作失败之后的回调函数)。
2、Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。
3、then方法返回的是一个新的Promise实例。,可进行链式调用。后一个回调函数会等前一个Promise对象的状态发生变化的时候,才会被调用
4、catch()方法,是.then(null, rejection)或者是.then(undefined, rejection)的别名,用于指定发生错误时的回调函数。
5、finally()方法,用于指定不管Promise对象最后的状态如何,都会执行的操作,在执行完then或catch指定的回调函数
6、all()方法,用于将多个Promise实例,包装成一个新的Promise实例,该方法接受一个数组作为参数,p1、p2、p3都是Promise实例,当三者的状态都变为fulfilled,all()的实例的状态才会变成fulfilled,只要有一个rejected,all的实例的状态就会变成rejected
7、race()方法,同样也是包装成一个新的Promise实例,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
8、any()方法,与race的方法很像,只有一点不同,就是不会因为某个Promise变成rejected装填而结束。
回调地狱
很烦。
apiA({
handeleSucess(resA){
apiB({
handleSucess(resB){
apiC({
handleSucess(resc)
})
}
})
}})
简单的实现promise
1.1简单实现promise
const PENDING = 'PENDING'; const RESOLVED = 'RESOLVED'; const REJECTED = 'REJECTED'; class PromiseA { constructor(executor) { this.status = PENDING; // 宏变量, 默认是等待态 this.value = undefined; // then方法要访问到所以放到this上 this.reason = undefined; // then方法要访问到所以放到this上
this.onResolvedCallbacks = [];// 专门存放成功的回调函数 this.onRejectedCallbacks = [];// 专门存放成功的回调函数
let resolve = (value) => { if (this.status === PENDING) {// 保证只有状态是等待态的时候才能更改状态 this.value = value; this.status = RESOLVED; // 监听回调函数,需要让成功的方法依次执行 this.onResolvedCallbacks.forEach(fn => fn()); } }; let reject = (reason) => { if (this.status === PENDING) { this.reason = reason; this.status = REJECTED; // 需要让失败的方法依次执行 this.onRejectedCallbacks.forEach(fn => fn()); } }; // 执行executor传入我们定义的成功和失败函数:把内部的resolve和reject传入executor中用户写的resolve, reject try { executor(resolve, reject); } catch(e) { console.log('catch错误', e); reject(e); //如果内部出错 直接将error手动调用reject向下传递 } } then(onfulfilled, onrejected) {
//解决onFufilled,onRejected不是函数的问题,给一个默认的回调函数。
onFufilled = typeof onFufilled === 'function'?onFufilled:y=>y
onRejected = typeof onRejected === 'function'?onRejected:err=>{ throw err ;}
if (this.status === RESOLVED) { onfulfilled(this.value); } if (this.status === REJECTED) { onrejected(this.reason); } // 处理异步的情况,pending的时候,把任务加入callback队列 if (this.status === PENDING) { // this.onResolvedCallbacks.push(onfulfilled); 这种写法可以换成下面的写法,多包了一层,这叫面向切片编程,可以加上自己的逻辑 this.onResolvedCallbacks.push(() => { // TODO ... 自己的逻辑 onfulfilled(this.value); }); this.onRejectedCallbacks.push(() => { // TODO ... 自己的逻辑 onrejected(this.reason); }); } } }
首先new Promise时,实例化一个对象,会执行constructor函数,然后传给promise的函数发送异步请求,会将异步请求发送到异步栈里, 接着调用了promise对象的then的属性,注册请求成功和注册请求失败的回调函数。
至此,同步任务执行结束。进入到异步任务里,异步执行resolve函数,此时state为pending状态,把转态置Resolved状态,并一一执行成功清单里面的任务。
调用:
let promise = new PromiseA((resolve, reject) => { setTimeout(() => { resolve('xxx'); }, 1000); }); // 发布订阅模式应对异步 支持一个promise可以then多次 promise.then((res) => { console.log('成功的结果1', res); }, (error) => { console.log(error); }); promise.then((res) => { console.log('成功的结果2', res); }, (error) => { console.log(error); });
1.2是先promise.then的链式调用
这个时候已经实现了基本的promise了。可以支持异步。但是还不支持链式调用。
下面实现链式调用。
1、then方法必须返回的必须是一个promise,这样才能保证链式调用
2、如果then内部的回调函数执行结果依然是一个promise,那就把这个promise的结果resolve出去
3、任何一个promise必须是resolve之后才能刚走到它的then方法,从而创建下一个promise。
4、什么时候走成功回调,then返回一个普通值或者一个成功的promise。
5、什么时候走失败的回调,?返回一个失败的promise,或者抛出异常,
class PromiseA { constructor(executor) { this.status = PENDING; // 宏变量, 默认是等待态 this.value = undefined; // then方法要访问到所以放到this上 this.reason = undefined; // then方法要访问到所以放到this上 // 专门存放成功的回调函数 this.onResolvedCallbacks = []; // 专门存放成功的回调函数 this.onRejectedCallbacks = []; let resolve = (value) => { if (this.status === PENDING) { // 保证只有状态是等待态的时候才能更改状态 this.value = value; this.status = RESOLVED; // 需要让成功的方法一次执行 this.onResolvedCallbacks.forEach(fn => fn()); } }; let reject = (reason) => { if (this.status === PENDING) { this.reason = reason; this.status = REJECTED; // 需要让失败的方法一次执行 this.onRejectedCallbacks.forEach(fn => fn()); } }; // 执行executor 传入成功和失败:把内部的resolve和 reject传入executor中用户写的resolve, reject try { executor(resolve, reject); // 立即执行 } catch (e) { console.log('catch错误', e); reject(e); //如果内部出错 直接将error 手动调用reject向下传递 } } then(onfulfilled, onrejected) {
onFufilled = typeof onFufilled === 'function' ? onFufilled : y => y;
onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err};
// 上面的两个判断用来解决then函数中的参数不是函数的问题,
// 为了实现链式调用,创建一个新的promise let promise2 = new Promise((resolve, reject) => { if (this.status === RESOLVED) { // 执行then中的方法 可能返回的是一个普通值,也可能是一个promise,如果是promise的话,需要让这个promise执行 // 使用宏任务把代码放在一下次执行,这样就可以取到promise2,为什么要取到promise2? 这里在之后会介绍到 setTimeout(() => { try {
// 下面两行是一个规整化(用来处理then里面可能是promise的),
// 若不考虑规整化,可直接使用下面注释的一行,
// onfufilled(this.reason)
let x = onfulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } catch (e) { // 一旦执行then方法报错就走到下一个then的失败方法中 console.log(e); reject(e); } }, 0); } if (this.status === REJECTED) { setTimeout(() => { try { let x = onrejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); } // 处理异步的情况 if (this.status === PENDING) { // 这时候executor肯定是有异步逻辑 this.onResolvedCallbacks.push(() => { setTimeout(() => { try { let x = onfulfilled(this.value); // 注意这里传入的是promise2的resolve和reject resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }); this.onRejectedCallbacks.push(() => { setTimeout(() => { try { let x = onrejected(this.reason); resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); } }, 0); }); } }); return promise2; } }
- 为什么外面要包一层
setTimeout
?:因为Promise本身是处理一个异步方法,必须得在执行栈执行完了在去取他的值,所以,所有的返回值都得包一层异步setTimeout。 resolvePromise
是什么?因为then返回的是promise对象的,如果是promise对象,需要将他拆解,直到不是promise对象,取出其中的值。
resolvePromise函数,用来规程化的,用来处理then内部回调函数是promise函数的,
function resolvePromise(promise2, x, resolve, reject) { if((typeof x === 'object' && x != null) || typeof x === 'function') { // 有可能是promise, 如果是promise那就要有then方法 let then = x.then; if (typeof then === 'function') { // 到了这里就只能认为他是promise了 // 如果x是一个promise那么在new的时候executor就立即执行了,就会执行他的resolve,那么数据就会传递到他的then中 then.call(x, y => {// 当前promise解析出来的结果可能还是一个promise, 直到解析到他是一个普通值 resolvePromise(promise2, y, resolve, reject);// resolve, reject都是promise2的 }, r => { reject(r); }); } else { // 出现像这种结果 {a: 1, then: 1} resolve(x); } } else { resolve(x); } }
1.3catch方法
catch方法其实就是没有成功回调的then方法,
//异常处理 用于指定发生错误时的回调函数。 //promise抛出一个错误,就被catch()方法指定的回调函数捕获 Promise.prototype.catch = function (onRejected) { return this.then(undefined, onRejected) }
1.4 finally方法
Promise.finally()方法,用于指定不管Promise对象的最后的状态如何,都会执行的操作;
finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的Promise状态到底是fufilled还是rejected,这表明,finally方法里面的操作,应该与状态是无关的,不依赖Promise的执行结果。因此可以绑定此事件在当前的promise实例的then方法上,在成功的时候回调传入的函数,在失败的时候也进行回调传入的参数。
/** * finally 函数 promise m每次执行后都会进行执行 * @param {*} cb */ PromiseA.prototype.finally = function (cb) { //finally 传入函数,无论成功或者失败都会执行 return this.then(data => { //Promise.resolve 可以等待这个promise完成 return Promise.resolve(cb().then(() => data)) }, err => { //失败的时候也执行 return Promise.reject(cb().then(() => { throw err })) }) }
1.5 Promise.all()方法
Promise.all可用于接收一个数组作为参数,参数可以不是数组,但是必须有Iterator接口,且返回的每个成员都是Promise的实例,他的结果是根据传入的数据进行变化的
//all方法(获取所有的promise,都执行then,把结果放到数组,一起返回),所有的成功才会成功,一个失败就会失败 Promise.all = function(promiseList){ let arr = [] let i = 0 function processData(index,data){ arr[index] = data i++ if(i == promises.length){ resolve(arr) } } return new Promise((resolve,reject)=>{ for(let i=0;i<promises.length;i++){ promises[i].then(data=>{ processData(i,data) },reject) } }) }
其原理就是将参数中的数组取出遍历,每当执行成功都会执行
processData
方法,processData
方法就是用来记录每个Promise的值和它对应的下标,当执行的次数等于数组长度时就会执行resolve,把arr的值给then。这里会有一个坑,如果你是通过arr数组的长度来判断他是否应该resolve的话就会出错,为什么呢?因为js数组的特性,导致如果先出来的是1位置上的值进arr,那么0位置上也会多一个空的值,所以不合理。
1.5 race方法,
//race方法 Promise.race = function(promises){ return new Promise((resolve,reject)=>{ for(let i=0;i<promises.length;i++){ promises[i].then(resolve,reject) } }) }