Promise原理与仿写Promise(异步二)
Promise基本功能
详情可以回顾上一期异步专题笔记
根据Promise基本功能我们可以进行一个仿写实践
-
三种状态
- pending
- fulfilled
- rejected
-
Promise 对象的then 方法
-
then的2种参数
-
then的三种返回值
三种状态
首先我们可以创建一个MyPromise类,它传入的参数是一个函数,函数的参数是 reslove 和 reject。他的默认状态是pending , value 是undefined。
在页面创建一个MyPromise 实例,可以得出这样效果,pending效果实现了
class Mypromise {
constructor(handle) {
this.status = 'pending';
this.value = undefined
}
}
//result
Mypromise
status: "pending"
value: undefined
__proto__: Object
fulfilled,rejected 观察一个promise对象。它传入的参数是一个函数,函数的参数是 reslove 和 reject。首先它要执行传入的函数,再执行reslove() 和reject()。所以它里面利用的是一个高阶函数。执行reslove() 和reject() 会改变它的状态和value 。
注意,在MyPromise 执行reslove() 和reject() 改变状态和value 的this指向MyPromise ,但是在外部执行reslove() 和reject() 的this 指向不固定,本实例指向是windows。里面没有 this.status 和this.value 。所以在内部时候我们要把 reslove() 和reject() bind(this)一下
//Mypromise.js
class Mypromise {
constructor(handle) {
this.status = 'pending';
this.value = undefined;
handle(this._resolve.bind(this),this._reject.bind(this))
}
_resolve (val){
this.status = 'fulfilled';
this.value = val;
}
_reject (val){
this.status = 'rejected';
this.value = val;
}
}
// myPromise.html
<script>
const mPro = new Mypromise((res,rej)=>{
// res('res...');
rej('rej...')
});
console.log(mPro)
</script>
//result
Mypromise
status: "fulfilled"
value: "res..."
__proto__: Object
Mypromise
status: "rejected"
value: "rej..."
__proto__: Object
Promise 对象的then 方法 , then的2种参数
then是Promise 方法,我们可以在类里面直接写then 方法。但是遇到一个问题。我们的then的2种参数 onResolved onRejected 方法到底是不是在 then方法里面执行。
我们先看以代码,状态改变了就执行。这个时候调用resolve 和 reject 改变状态是同步任务是没有问题
//Mypromise.js / then()
if (this.status === "fulfilled") {
onResolved && onResolved(this.value);
} else if (this.status === 'rejected') {
onRejected && onRejected(this.value);
}
console.log(this.status );
// myPromise.html
let p = new KPromise((resolve, reject) => {
// resolve()
//setTimeout(() => {
// 调用onResolved
resolve("resolveValue") //设置状态
// }, 3000)
// reject("rejectValue");
})
p.then(res => {
console.log("onResolved", res);
}, err => {
console.log("onRejected", err);
})
但是如果我们在调用时候 是延迟执行。会发现,then 里面是同步代码,改变状态变成异步代码,以上的。那么会出现一个问题,就是先执行then 里面的代码,在改变状态。那么函数的状态永远都是pending,就不行执行onResolved,onRejected 方法。
let p = new KPromise((resolve, reject) => {
// resolve()
setTimeout(() => {
// 调用onResolved
resolve("resolveValue") //设置状态
}, 3000)
// reject("rejectValue");
})
由此我们可以看出, onResolved 及 onRejected 不是在then里执行的;他是和 resolve 及 reject 相关;所以我们在then 方法中可以先保存这个函数,但是不执行,等status状态改变时候执行。
那么我们可以利用异步和同步执行顺序不同改变
//myPromise.html
const mPro = new Mypromise((res,rej)=>{
setTimeout(()=>{
res('res...');
},1000)
//rej('rej...');
});
mPro.then((res) =>{
console.log('onres...');
},(err)=>{
console.log('err...');
})
console.log(mPro);
//Mypromise.js
_resolve (val){
this.status = 'fulfilled';
this.value = val;
setTimeout(()=>{
this.onResolved (val);
})
}
_reject (val){
this.status = 'rejected';
this.value = val;
setTimeout(()=>{
this.onRejected (val);
})
}
then(onResolved,onRejected){
this.onResolved = onResolved;
this.onRejected = onRejected
}
then带来两个小问题
-
多个then调用问题(注意不是链式调用)队列;
当调用多个then时候,后面的函数会覆盖前面的函数。但是原生Promise,却都会执行。这个思考,它不是简单的函数赋值保存,而是一个队列保存的.
//mypromise.js _resolve (val){ this.status = 'fulfilled'; this.value = val; const run = () => { this.onResolvedQueue.forEach(cb =>{ cb(val); }) } setTimeout(run); } //then then(onResolved,onRejected){ this.onResolvedQueue.push(onResolved); this.onRejectedQueue.push(onRejected); } // html mPro.then((res)=>{ console.log(111) }) mPro.then((res)=>{ console.log(222) }) -
KPromise执行顺序问题;(宏任务、微任务相关);
原生中Promise 是微任务,而自己的封装的myPromise 是宏任务,所以在有些任务执行时候会有问题.
**宏任务执行中在遇到新宏任务,会让新宏任务放在宏任务队列末尾,但是一个宏任务执行完成后,执行微任务队列,在执行微任务队列中,遇到新的微任务,它不会放在宏任务队列末尾,而是放在当前一个宏任务执行完成后执行的微任务队列里面.**所以新加的宏任务和新加的微任务过来,新加的微任务会先于新加的宏任务执行
微任务:
- Promise
- mutationObserver
- process.nextTick
宏任务:
- settimeout
- setInterval
所以我们要将我们改写的KPromise变成微任务,可以利用mutationObserver , 该方法是监听节点变化.
mutationObserver 使用,document.body 属性变化了,就触发MutationObserver 的callback
setTimeout(() => {
console.log("settimeout");
});
let callback = function(){
console.log("微任务执行了");
}
let ob = new MutationObserver(callback)
ob.observe(document.body,{
attributes:true
})
document.body.setAttribute("123",Math.random());
改写
_resolve (val){
this.status = 'fulfilled';
this.value = val;
const run = () => {
this.onResolvedQueue.forEach(cb =>{
cb(val);
})
}
// setTimeout(run);
let ob = new MutationObserver(run);
ob.observe(document.body,{
attributes:true
})
document.body.setAttribute("attr",Math.random());
}
then的三种返回值和链式调用(难点)
链式调用返回一个实例对象
then的三种返回值(难点)
then 方法在执行最后必须返回一个新的 Promise 对象
重点(难点)
返回
Promise对象会立即调用并执行,如果这个时候,直接去执行该对象的resolve或者reject方法都会导致后续的then也立即被调用我们需要对原
fulfilledHandler和rejectedHandler进行包装,把它们和新Promise对象的resolve和reject方法分别放置到新的函数中,并把这个新的函数添加到原有任务队列中调用简而言之:把新返回的
Promise对象的resolve和reject与then中执行的fulfilledHandler和rejectedHandler添加到一个任务队列中执行,这样才能使用原有的then执行完成以后才执行新的Promise中的then
上面是默认情况下的处理情况,其实 then 方法的处理更为复杂
当一个
Promise完成(fulfilled)或者失败(rejected),返回函数将被异步调用(由当前的线程循环来调度完成)。具体的返回值依据以下规则返回:
- 如果then中的回调函数没有返回值,那么then返回的Promise将会成为接受状态,并且该接受状态的回调函数的参数值为 undefined。
- 如果then中的回调函数返回一个值,那么then返回的Promise将会成为接受状态,并且将返回的值作为接受状态的回调函数的参数值。
- 如果then中的回调函数抛出一个错误,那么then返回的Promise将会成为拒绝状态,并且将抛出的错误作为拒绝状态的回调函数的参数值。
- 如果then中的回调函数返回一个已经是接受状态的Promise,那么then返回的Promise也会成为接受状态,并且将那个Promise的接受状态的回调函数的参数值作为该被返回的Promise的接受状态回调函数的参数值。
- 如果then中的回调函数返回一个已经是拒绝状态的Promise,那么then返回的Promise也会成为拒绝状态,并且将那个Promise的拒绝状态的回调函数的参数值作为该被返回的Promise的拒绝状态回调函数的参数值。
- 如果then中的回调函数返回一个未定状态(pending)的Promise,那么then返回Promise的状态也是未定的,并且它的终态与那个Promise的终态相同;同时,它变为终态时调用的回调函数参数与那个Promise变为终态时的回调函数的参数是相同的。
then(onResolved,onRejected){
// this.onResolvedQueue.push(onResolved);
// this.onRejectedQueue.push(onRejected);
return new Mypromise((resolve,reject)=>{
// 直接执行;错误的;
// let res = onResolved && onResolved();
// // 对象;
// if(res instanceof Mypromise){
// return res;
// }
// // 普通值;
// resolve(res);
// push不去执行;执行和逻辑分开
this.onResolvedQueue.push(val=>{
let res = onResolved && onResolved(val);
if(res instanceof Mypromise){
// 返还 Mypromise对象
// return res.then(res=>{
// resolve(res)
// });
// console.log(resolve)
return res.then(resolve);
}
resolve(res);
})
this.onRejectedQueue.push(val=>{
onRejected && onRejected(val);
reject(val);
})
// reject("err")
})
}
Promise 其他静态方法
- promise.resolve
- promise.reject
- promise.catch
- promise.all
- promise.race
- promise.finally
catch(onRejected){
this.then(undefined,onRejected);
}
static resolve(val){
return new KPromise(resolve=>{
resolve(val);
})
}
static reject(val){
return new KPromise((resolve,reject)=>{
reject(val);
})
}
static all(lists){
let arr = [];
return new KPromise((resolve)=>{
lists.forEach(item=>{
item.then(res=>{
arr.push(res);
if(arr.length===lists.length){
resolve(arr);
}
})
})
})
}
static race(lists){
return new KPromise((resolve,reject)=>{
lists.forEach(item=>{
item.then(res=>{
resolve(res);
},err=>{
reject(err);
})
})
})
}
finally(cb) {
this.then(cb, cb);
}
