【学习记录】promise原理分析(二)

127 阅读9分钟

手写promise— — 弄清promise原理。

       在对promise有了一定的了解之后,对于“手写一个promise”内心不禁感到疑惑— —难道new 一个promise不香么?其实,通过手写一个promise这样一个过程,能够加深对promise原理的理解,内心的疑惑也豁然开朗。

一、promise所解决的问题

     在promise出现之前,解决异步请求一般有如下的方法代码

let fs = require('fs')

fs.readFile('./name.txt','utf8',function(err,data){
  fs.readFile(data, 'utf8',function(err,data){
    fs.readFile(data,'utf8',function(err,data){
      console.log(data);
    })
  })
})

以上是通过层层嵌套而得到回调的结果,这样的代码难以阅读以及维护,并存在回调地狱这一个令人头疼的问题。而之所以会产生回调地狱,原因在于:

【1】嵌套调用

【2】处理多个异步请求并发

promise由此而来:

【1】通过promise的链式调用消灭嵌套调用

【2】使用promise.all获取合并多个任务的错误处理

如上面的例子可采用如下promise实现

let fs = require('fs')

function read(filename) {
  return new Promise((resolve, reject) => {
    fs.readFile(filename, 'utf8', (err, data) => {
      if (err) reject(err);
      resolve(data);
    })
  })
}

read('./name.txt').then((data)=>{
  return read(data) 
}).then((data)=>{
  return read(data)  
}).then((data)=>{
    console.log(data);
},err=>{
    console.log(err);
})

二、从零开始,手写一个promise

1、最基础的promise使用方式如下所示:

const p1 = new Promise((resolve, reject) => {
  console.log('create a promise');
  resolve('成功了');
})

console.log("after new promise");

const p2 = p1.then(data => {
  console.log(data)
  throw new Error('失败了')
})

const p3 = p2.then(data => {
  console.log('success', data)
}, err => {
  console.log('faild', err)
})

输出结果:

"create a promise"
"after new promise"
"成功了"
"faild Error: 失败了"

那么由以上相关的介绍以及promise的基本原理可知promise具有以下基本特征:

【0】promise 有三个状态:pending,fulfilled,or rejected;「规范 Promise/A+ 2.1」

【1】new promise时, 需要传递一个executor()执行器,执行器立即执行;

【2】executor接受两个参数,分别是resolve和reject;

【3】promise 的默认状态是 pending;

【4】promise 有一个value保存成功状态的值,可以是undefined/thenable/promise;「规范 Promise/A+ 1.3」

【5】promise 有一个reason保存失败状态的值;「规范 Promise/A+ 1.5」

【6】promise 只能从pending到rejected, 或者从pending到fulfilled,状态一旦确认,就不会再改变;

【7】promise 必须有一个then方法,then 接收两个参数,分别是 promise 成功的回调 onFulfilled, 和 promise 失败的回调 onRejected;「规范 Promise/A+ 2.2」

【8】如果调用 then 时,promise 已经成功,则执行onFulfilled,参数是promise的value

【9】如果调用 then 时,promise 已经失败,那么执行onRejected, 参数是promise的reason

【10】如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调onRejected;

1、由此,可先试着勾勒出一个promise:

     由promise基本原理可以知道promise有三种状态,为了方便使用把它们定义成常量:

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class MyPromise {
    constructor(executor) {
        //控制状态,使用了一次之后,接下来的都不被使用
        //默认值为PENDING
        this.state = PENDING;
        // 存放成功状态的值,默认为 null
        this.value = null; 
        // 存放失败状态的值,默认为 null
        this.reason = null;

        // 定义resolve函数,成功时调用此方法
        const resolve = value => {
            // 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 
            if (this.state === PENDING) {
                this.value = value;
                this.state = FULFILLED;
            }
        }

        // 定义reject函数,失败时调用此方法
        const reject = value => {
             // 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject
            if (this.state === PENDING) {
                this.reason = value;
                this.state = REJECTED;
            }
        }

        // executor方法可能会抛出异常,需要捕获
        try {
             // 将resolve和reject函数给使用者  
            executor(resolve, reject);
        } catch (error) {
            // 如果在函数中抛出异常则将它注入reject中
            // 发生异常时执行失败逻辑
            reject(error);
        }
    }
}

      对于上诉所描述的类的说明:

【1】executor:这是实例Promise对象时在构造器中传入的参数,形如function(resolve,reject){};

【2】state:Promise的状态,一开始为默认的pendding状态,每当调用resolve和reject方法时,就会改变其值(pendding->fullfilled/pendding->rejected),在后面的then方法中会用到;

【3】value:resolve回调成功后,调用resolve方法里面的参数值;

【4】reason:reject回调成功后,调用reject方法里面的参数值;

【5】resolve:声明resolve方法在构造器内,通过传入的executor方法传入其中,用以给使用者回调;

【6】reject:声明reject方法在构造器内,通过传入的executor方法传入其中,用以给使用者回调;

2、then

     then是获取到promise中的resolve或reject结果,由此可知,then就需要传入两个参数,成功回调函数和失败回调函数:

then(onFulfilled, onRejected) {
    if (this.state === FULFILLED) {
        onFulfilled(this.value)
    }
    if (this.state === REJECTED) {
        onRejected(this.reason)
    }
}

       然后测试一下:

const mp = new MyPromise((resolve, reject)=> {
    resolve('success!!');
})
mp.then((suc)=> {
console.log(11111, suc);
}, (err)=> {
console.log('eror!!', err)
})

// 11111 'success!!'

 可以发现,在测试中可以正常输出,但是对于异步函数却会出现问题,如下所示

const mp = new MyPromise((resolve, reject)=> {
    setTimeout(()=> {
        resolve('success!!');    }, 0)})
mp.then((suc)=> {
console.log(11111, suc);
}, (err)=> {
console.log('eror!!', err)
})

       因为 promise 调用 then 方法时,当前的 promise 并没有成功,一直处于 pending 状态。所以如果当调用 then 方法时,当前状态是 pending,我们需要先将成功和失败的回调分别存放起来,在executor()的异步任务被执行时,触发 resolve 或 reject,依次调用成功或失败的回调。由此分析,进行优化代码(使用callback):

// 存放成功回调的函数
this.onFulfilledCallbacks = [];
// 存放失败回调的函数
this.onRejectedCallbacks = [];

const resolve = value => {
    if (this.state === PENDING) {
        this.value = value;
        this.state = FULFILLED;
        // 依次将对应的函数执行
        this.onFulfilledCallbacks.map(fn => fn());
    }
}

const reject = value => {
    if (this.state === PENDING) {
        this.value = value;
        this.reason = REJECTED;
        // 依次将对应的函数执行
        this.onRejectedCallbacks.map(fn => fn());
    }
}

then里面添加:

then(onFulfilled, onRejected) {
    // ... 
    if(this.state === PENDING) {
         // 如果promise的状态是 pending,需要将 onFulfilled 和 onRejected 函数存放起来
        this.onFulfilledCallbacks.push(()=> {
            onFulfilled(this.value);
        });
        // 如果promise的状态是 pending,需要将 onFulfilled 和 onRejected 函数存放起来,
        this.onRejectedCallbacks.push(()=> {
            onRejected(this.value);
        })
    }
}

再进行测试可以成功输出,由此可以解决异步的问题。

3、then 的链式调用&值穿透特性

       可以知道,promise 的优势在于可以链式调用。在使用 Promise 的时候,当 then 函数中 return 了一个值,不管是什么值,都能在下一个 then 中获取到,这就是所谓的then 的链式调用。而且,当我们不在 then 中放入参数,例:promise.then().then(),那么其后面的 then 依旧可以得到之前 then 返回的值,这就是所谓的值的穿透。那么具体如何实现?

对于链式调用问题——再返回一个promise实例即可;而对于不传参的问题,则是设置默认值的问题。具体思路(结合 Promise/A+ 规范)如下所示:

【1】then 的参数 onFulfilledonRejected 可以缺省,如果 onFulfilled 或者 onRejected不是函数,将其忽略,且依旧可以在下面的 then 中获取到之前返回的值;「规范 Promise/A+ 2.2.1、2.2.1.1、2.2.1.2」

【2】promise 可以 then 多次,每次执行完 promise.then 方法后返回的都是一个“新的promise";「规范 Promise/A+ 2.2.7」

【3】如果 then 的返回值 x 是一个普通值,那么就会把这个结果作为参数,传递给下一个 then 的成功的回调中;

【4】如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个 then 的失败的回调中;「规范 Promise/A+ 2.2.7.2」

【5】如果 then 的返回值 x 是一个 promise,那么会等这个 promise 执行完,promise 如果成功,就走下一个 then 的成功;如果失败,就走下一个 then 的失败;如果抛出异常,就走下一个 then 的失败;「规范 Promise/A+ 2.2.7.3、2.2.7.4」

【6】如果 then 的返回值 x 和 promise 是同一个引用对象,造成循环引用,则抛出异常,把异常传递给下一个 then 的失败的回调中;「规范 Promise/A+ 2.3.1」

【7】如果 then 的返回值 x 是一个 promise,且 x 同时调用 resolve 函数和 reject 函数,则第一次调用优先,其他所有调用被忽略;「规范 Promise/A+ 2.3.3.3.3」

具体实现代码如下所示:

const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

const resolvePromise = (promise2, x, resolve, reject) => {
  // 自己等待自己完成是错误的实现,用一个类型错误,结束掉 promise  Promise/A+ 2.3.1
  if (promise2 === x) { 
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
  }
  // Promise/A+ 2.3.3.3.3 只能调用一次
  let called;
  // 后续的条件要严格判断 保证代码能和别的库一起使用
  if ((typeof x === 'object' && x != null) || typeof x === 'function') { 
    try {
      // 为了判断 resolve 过的就不用再 reject 了(比如 reject 和 resolve 同时调用的时候)  Promise/A+ 2.3.3.1
      let then = x.then;
      if (typeof then === 'function') { 
        // 不要写成 x.then,直接 then.call 就可以了 因为 x.then 会再次取值,Object.defineProperty  Promise/A+ 2.3.3.3
        then.call(x, y => { // 根据 promise 的状态决定是成功还是失败
          if (called) return;
          called = true;
          // 递归解析的过程(因为可能 promise 中还有 promise) Promise/A+ 2.3.3.3.1
          resolvePromise(promise2, y, resolve, reject); 
        }, r => {
          // 只要失败就失败 Promise/A+ 2.3.3.3.2
          if (called) return;
          called = true;
          reject(r);
        });
      } else {
        // 如果 x.then 是个普通值就直接返回 resolve 作为结果  Promise/A+ 2.3.3.4
        resolve(x);
      }
    } catch (e) {
      // Promise/A+ 2.3.3.2
      if (called) return;
      called = true;
      reject(e)
    }
  } else {
    // 如果 x 是个普通值就直接返回 resolve 作为结果  Promise/A+ 2.3.4  
    resolve(x)
  }
}

class Promise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks= [];

    let resolve = (value) => {
      if(this.status ===  PENDING) {
        this.status = FULFILLED;
        this.value = value;
        this.onResolvedCallbacks.forEach(fn=>fn());
      }
    } 

    let reject = (reason) => {
      if(this.status ===  PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn=>fn());
      }
    }

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

  then(onFulfilled, onRejected) {
    //解决 onFufilled,onRejected 没有传值的问题
    //Promise/A+ 2.2.1 / Promise/A+ 2.2.5 / Promise/A+ 2.2.7.3 / Promise/A+ 2.2.7.4
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
    //因为错误的值要让后面访问到,所以这里也要跑出个错误,不然会在之后 then 的 resolve 中捕获
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
    // 每次调用 then 都返回一个新的 promise  Promise/A+ 2.2.7
    let promise2 = new Promise((resolve, reject) => {
      if (this.status === FULFILLED) {
        //Promise/A+ 2.2.2
        //Promise/A+ 2.2.4 --- setTimeout
        setTimeout(() => {
          try {
            //Promise/A+ 2.2.7.1
            let x = onFulfilled(this.value);
            // x可能是一个proimise
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            //Promise/A+ 2.2.7.2
            reject(e)
          }
        }, 0);
      }

      if (this.status === REJECTED) {
        //Promise/A+ 2.2.3
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e)
          }
        }, 0);
      }

      if (this.status === PENDING) {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              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;
  }
}

进行以下测试:

const promise = new Promise((resolve, reject) => {
  reject('失败');
}).then().then().then(data=>{
  console.log(data);
},err=>{
  console.log('err',err);
})

可正常输出:

"失败 err"

到这里,手写promise的关键部分也基本完成,最重要的是要吃透then 的链式调用和值的穿透。

4、promise的其他api方法实现

【1】catch

//catch方法
catch(onRejected){
  return this.then(null,onRejected)
}

【2】finally方法

      finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。具体看代码

finally(fn) {
    return this.then(value => {
        fn();
        return value;
    }, reason => {
        fn();
        throw reason;
    });
};

未完待续...