Promise原理与仿写Promise(异步二)

410 阅读6分钟

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状态改变时候执行。

那么我们可以利用异步和同步执行顺序不同改变

sHOBi4.png

//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 是宏任务,所以在有些任务执行时候会有问题.

    sHvXTK.png

sHvvFO.jpg

**宏任务执行中在遇到新宏任务,会让新宏任务放在宏任务队列末尾,但是一个宏任务执行完成后,执行微任务队列,在执行微任务队列中,遇到新的微任务,它不会放在宏任务队列末尾,而是放在当前一个宏任务执行完成后执行的微任务队列里面.**所以新加的宏任务和新加的微任务过来,新加的微任务会先于新加的宏任务执行

微任务:

  • 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 也立即被调用

我们需要对原 fulfilledHandlerrejectedHandler 进行包装,把它们和新 Promise 对象的 resolvereject 方法分别放置到新的函数中,并把这个新的函数添加到原有任务队列中调用

简而言之:把新返回的 Promise 对象的 resolverejectthen 中执行的 fulfilledHandlerrejectedHandler 添加到一个任务队列中执行,这样才能使用原有的 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);
  }

github

传送门