## Promise原理解析

Promise由来

Promise出现以前,我们通常主主要的解决异步问题的方式就是通过回调嵌套,在异步逻辑执行完毕之后通过回调函数的方式获取异步执行结果. 层层回调嵌套会使得代码逻辑不直观也不利于后期开发维护.

fs.readFile("./a.txt", (err, data) => {
  fs.readFile(data, (err, data) => {
  		fs.readFile(data, (err, data) => {
        //回调黑洞
		})
	})
})
复制代码

Promise巧妙的将异步回调形式的层层嵌套,转变成同步回调形式的链式调用.本质上Promise内部用的还是回调函数的方式,解决异步问题, 但是可以通过同步的方式体现.

new Promise((reslove, reject)=> {
  //异步逻辑
}).then(
//异步逻辑
).then(
//异步逻辑
).then(

)
复制代码

同步版的Promise

直接上Promise的源码吧, Promise的源码都是根据Promise/A+规范来实现的

const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected'

function Promise(executor) {
  	/**
  	*Promise对象内部有一个状态, 默认为初始状态pending,由Promise的执行结果操作状态的改变, 而且整个过程状态只能变化一次,要么是由等待态pending变成成功态resolved,要么是由等待态变成失败态rejected;
  	*/
    this.state = PENDING;
    this.value = undefined;
    this.reason = undefined;

    let resolve = (value) => {
        if (this.state === PENDING) { //只能是在pending的时候才能改变状态
            this.state = RESOLVED;
            this.value = value;
        }
    }

    let reject = (reason) => {
        if (this.state === PENDING) {
            this.state = REJECTED;
            this.reason = reason;
        }
    }

    /**
    * Promise接收一个函数作为参数,这个函数会在new Promise时立即执行, 并且该函数接收两个函数作为参数,在	Promise操作成功的时候调用第一个参数resolve, 在操作失败的时候调用reject
    */
    try {
        executor(resolve, reject)
    } catch (e) {
        reject(e)
    }
}

Promise.prototype.then = function (onResolved, onRejected) {
  /**
  * 生成的每个Promise对象都具有then方法, then方法也同样接受两个函数操作参数,在调用then方法时,如果此时的状态为resolved则调用执行第一个函数onResolved, 当此时的状态时rejected时执行第二个函数onRejected
  */
    if (this.state === RESOLVED) {
        onResolved(this.value)
    }

    if (this.state === REJECTED) {
        onRejected(this.reason)
    }
}
复制代码

但是上面的Promise是一个基础版本,只能解决同步问题, 因为生成的Promise对象在调用then方法时,此时的状态state已经发生改变,可以执行对应的逻辑, 但是思考一下,如果executor函数内执行的是一个异步操作, 那么生成promise对象在调用then时,改变状态的两个函数resolvereject都没有被调用, 此时的状态state还是等待态pending.

通常我们遇到这种问题的处理方式都是通过回调来解决, 可以先把then方法内的两个函数存放起来, 在异步操作执行完毕后,即在executor接收的两个参数函数被调用时,再回过头去执行then内暂存的方法:

const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected'

function Promise(executor) {

    this.state = PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = []; //存放then的第一个参数, 即状态为成功时要调用的回调函数
    this.onRejectedCallbacks = []; //存放then的第二个参数, 即状态为失败时要调用的回调函数

    let resolve = (value) => {
        if (this.state === PENDING) {
            this.state = RESOLVED;
            this.value = value;
          //在调用resolve,即在Promise状态为成功时执行then内的onResolved函数
            this.onRejectedCallbacks.forEach(fn => fn())
        }
    }

    let reject = (reason) => {
        if (this.state === PENDING) {
            this.state = REJECTED;
            this.reason = reason;
          //在调用reject,即在Promise状态为失败时执行then内的onRejected函数
            this.onRejectedCallbacks.forEach(fn => fn())
        }
    }

    try {
        executor(resolve, reject)
    } catch (e) {
        reject(e)
    }
}

Promise.prototype.then = function (onResolved, onRejected) {
    if (this.state === RESOLVED) {
        onResolved(this.value)
    }

    if (this.state === REJECTED) {
        onRejected(this.reason)
    }
		//对于异步操作, 那么此时的状态肯定还是等待态, 先将处理函数存放, 带异步操作执行完毕后,再回过来执行
    if (this.state === PENDING) {
        this.onResolvedCallbacks.push(onResolved)
        this.onRejectedCallbacks.push(onRejected)
    }
}
复制代码

这里大家可能有些疑问

  1. 代码第10、11行为啥要用数组接收? 本来就一个函数, 直接用个变量保存不就行了么?

    //同一个promise对象的then方法可以被多次调用, 当多次调用then方法时,就需要把每个回调都接收
    var promise = new Promise(/**/)
    promise.then()
    promise.then()
    复制代码
  2. 为啥在调用执行then方法时,同步操作的状态改变, 而异步操作状态没改变还是pending?

    var p = new Promise((resolve, reject) => { /* 代码块1 */})
    p.then(/*代码块2*/)
    复制代码

    先理一下上面这块假代码的执行顺序: 在用new操作符调用Promise时, 先执行代码块1 ,执行完毕之后生成一个对象p,然后对象p调用执行then时,开始执行代码块2; 如果上面代码块中的操作都是同步的,那就没啥悬念,代码从上到下依次执行. 但是当代码块1是一个异步操作时,js引擎的执行顺序是先把当前的同步的代码执行完,才会去掉用执行异步代码, 所以如果代码块1是异步操作,那么在调用then方法时,状态还没有改变.

继续, 还没完…, 接下来的才是重点.

Promise的链式调用和值的穿透特性

我们在使用Promise时发现他的then方法可以一直不断的链式调用,而且上一个then方法内参数的返回值能够在下一个then方法内使用,甚至下下个then方法内使用,这里是如何做到的呢?

image-20210522161247744

链式调用简单, 我们直接在调用then方法时再返回一个promise对象就能实现链式调用了;对于then方法的值的穿透特性,如果我们能够把当前then的参数函数返回值传递到新返回的promise对象中,当这个新返回的promise对象调用自己的resolve/reject方法后不就可以在新返回的promise对象的then中使用了么, 有点绕哦……还是直接上代码吧; 由于构造函数Promise的代码还是上面那块,没有变化这里就不复制过来了, 这里变化的主要是then函数,和处理值的resolvePromise函数.

根据Promise/A+规范一个Promise必须提供一个then函数,用于处理它的valuereason, 并且这个Promise函数接收两个可选的函数作为参数promise.then(onFulfilled, onRejected)

  1. 如果onFulfilled或者onRejected不是一个函数, 那么就直接忽略掉

  2. 如果onFulfilled是一个函数

    它必须在promise的状态是resolved之后被调用,并且用promisevalue值作为它的第一参数

    它肯定不会在promise的状态是resolved之前被调用

    它的调用肯定不会超过一次

  3. 如果onRejected是一个函数

    它必须在promise的状态是rejected之后被调用,并且用promisereason值作为它的第一参数

    它肯定不会在promise的状态是rejected之前被调用

    它的调用肯定不会超过一次

  4. onFulfilled或者onRejected要在当前执行上下文的代码执行完毕之后再被调用(可以理解为异步调用)

  5. onFulfilled或者onRejected必须作为一个函数被调用

  6. then函数可能会被同一个promise对象多次调用

    如果当promise的状态是resolved, 所有各自的onFulfilled方法都会被按照调用then的顺序依次调用执行

    如果当promise的状态是rejected, 所有各自的onFejected方法都会被按照调用then的顺序依次调用执行

  7. then函数必须返回一个promise即: promise2 = promise1.then(onFulfilled, onRejected)

    如果onFulfilled或者onRejected返回了一个值x, 那么执行resolvePromise(promise2, x)

    如果onFulfilled或者onRejected抛出了一个异常e,promise2必须变成rejected状态,并把这个e作为reason

    如果onFulfilled不是一个函数并且promise1是一个成功态resolved,promise2必须变成resolved状态,并且使用promise1value,作为promise2value

    如果onFulfilled不是一个函数并且promise1是一个失败态rejected,promise2必须变也成rejected状态,并且使用promise1reason,作为promise2reason

Promise.prototype.then = function (onResolved, onRejected) {
  //根据Promise/A+规定then方法的两个参数函数是可选的,而且如果传入的不是函数会忽略掉, 用空函数替代
    onResolved = typeof onResolved === 'function' ? onResolved : v => v
    onRejected = typeof onRejected === 'function' ? onRejected : err => {throw err }
    let promise2 = new Promise((resolve, reject) => {
        if (this.state === RESOLVED) {
          //为了拿到promise2,需要先执行完同步代码才能生成promise2对象, 否则promise2就是undefined
            setTimeout(() => {
              //try...catch只能捕获同步异常,无法捕获异步异常
                try {
                    const x = onResolved(this.value)
                    //处理及判断x值类型, 好决定到底用promise2的哪一个状态改变函数调用
                    resolvePromise(promise2, x, resolve, reject)
                } catch (e) {
                    reject(e)
                }
            }, 0)
        }
        if (this.state === REJECTED) {
            setTimeout(() => {
                try {
                    const x = onRejected(this.reason)
                    resolvePromise(promise2, x, resolve, reject)
                } catch (e) {
                    reject(e)
                }
            }, 0)
        }
        if (this.state === PENDING) {
            this.onResolvedCallbacks.push(() => {
                setTimeout(() => {
                    try {
                        const x = onResolved(this.value)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                }, 0)
            })
            this.onRejectedCallbacks.push(() => {
                setTimeout(() => {
                    try {
                        const x = onRejected(this.reason)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                }, 0)
            })
        }
    })
    return promise2
}
复制代码

resolvePromise函数用于处理then函数参数的返回值, 根据返回值类型决定返回的下一个Promise的状态,从而决定到底执行下一个then的哪个参数函数,根据Promise/A+规范

  1. 如果promise2x指向同一个对象,调用promise2reject并把这个异常作为promise2reason

  2. 如果返回的x值是一个promise, 那就采用它的状态

    如果x的状态是pending, promise2也必须保持pending状态,直到x的状态变成resolved或者rejected

    如果x的状态是resolved,调用promise2resolve方法,接收当前value作为第一个参数

    如果x的状态是rejected,调用promise2reject方法, 接收当前的reason作为第一个参数

  3. 如果返回的x是一个函数或对象

    判断是否有then属性,如果这个then属性是一个函数,那么就认定这个x是一个promise对象, 如果没有then属性,或者then属性不是一个函数, 那么也把x作为普通值进行处理,直接调用promise2resolve(x)

  4. 如果返回的x不是一个函数或对象,那么就作为普通值处理,直接调用promise2resolve(x)

function resolvePromise(promise2, x, resolve, reject) {
  //在一个promise中resolve/reject最多只能调用一次
    let called;
    if (promise2 === x) {
        if (called) return;
        called = true
        reject(new TypeError("TypeError"))
    }
    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
        try {
            let then = x.then
            if (typeof then === 'function') {
              //这里就认定x是一个promise, 因为没法再进一步判断了
                then.call(x, v => {
                    if (called) return;
                    called = true
                    resolvePromise(promise2, v, resolve, reject)
                }, r => {
                    if (called) return;
                    called = true
                    reject(r)
                })
            } else {
                if (called) return;
                called = true
                resolve(x)
            }
        } catch (e) {
            if (called) return;
            called = true
            reject(e)
        }
    } else {
        if (called) return;
        called = true
        resolve(x)
    }
}
复制代码

代码不是很复杂就不一句句解释了,这里就直接说我在刚开始学习时遇到的疑问

  1. then方法代码第3、4行为啥then函数的第一个参数缺席用空函数替代, 而第二个参数缺席却需要抛出个异常?

    首先then函数接收两个函数作为参数, 最终then接收的两个函数只会执行其中的一个,具体执行哪一个函数取决于当前Promise的内部状态, 当Promise内部状态为resolved时执行第一个函数, 当Promise内部状态为rejected时执行第二个函数, 此外需要注意当executor函数在执行过程中抛异常的话,内部状态也会变成rejected;

    根据Promise/A+规范,如果在执行promise1onFulfilled或者onRejected时抛出了一个异常e, 会使promise2的状态变成rejected,并调用执行promise2对象的onRejected

  2. 为啥then函数中的有些代码需要放在定时器函数里?

    为了拿到promise2, 就必须使promise2的构造函数执行完,所以只能让同步代码先执行完, 生成promise2对象, 再回过来执行构造函数里的异步代码.

  3. 为啥在resolvePromise代码里用then.call(x)调用, 而不是直接用x.then?

    // 如果是通过下面的方式定义的属性then,在第一次获取时不会报错, 但在第二次获取时就会报错
    let times = 1
     	Object.defineProperty(x, 'then',
     		getter() {
           	if(times>1) {
        			times++
    					return new Error()
      			}
    				return () => {}
    			}
    		)
     
    复制代码

参考

  1. Promise/A+
  2. 阮一峰的Promise 对象
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享