手写实现简版Promise

624 阅读5分钟

「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战」。

Promise是什么和Promise的基础用法

Promise是什么

Promise简单来说的话,可以理解成一个容器,里面放着某个未来才会结束的操作(一般是异步的)的结果。从javascript的语法来看,Promise是一个对象,它包含了异步操作的信息,并提供了统一的API,各类异步操作都能用相同的方式进行处理。

Promise的基础用法

最简单的用法如下,封装了一个读取文件的函数,把异步读取的内容在then里传递下去。

function readFile(name) {
    return new Promise((resolve, reject) => {
        fs.readFile(name, 'utf8', (error, data) => {
            if(error) reject(error);
            resolve(data);
        });
    });
}
readFile(A).then(data => {
    return readFile(B);
}).then(data => {
    return readFile(C);
}).then(data => {
    return readFile(D);
}).catch(reason => {
    console.log(reason);
});

Promise解决了什么问题

在这里扩展一点,为什么需要Promise这种异步处理的方式呢?其实就是为了解决以前callback处理异步导致的回调地狱问题。

let readFilePromise = readFile(A).then(data => readFile(B)); // 这里返回了一个promise
readFilePromise.then(/* 回调内部逻辑省略 */) // 回调函数延迟传入
  • 首先异步之后的回调不再是直接被申明的,而是通过后面的then方法传入,即延迟传入。
  • 根据then中回调函数的传入值创建新的Promise,然后把返回的Promise穿透到外层,以供后续的调用。这里的readFilePromise指的就是内部返回的Promise,然后在readFilePromise后面可以依次完成链式调用。这便是then的链式调用和返回值穿透的效果。
  • Promise采用错误冒泡的方式处理执行过程中发生的报错。这样前面产生的错误会一直向后传递,直到被catch方法接收到,避免在每个回调里重复检查错误。 这样就解决了多层嵌套的问题,代码显得更清爽,并且更符合人的线性思维。

Promise的手写实现

在手动实现 Promise 之前,还必须先了解 Promise/A+ 规范。只有对规范有了明确的认知才能更好的实现Promise。

Promise/A+ 规范

  1. Promise是一个具有then方法的函数或者对象,这个then方法可以用来访问它的值或者拒绝原因。
  2. Promise内部有value代表返回值和reason代表拒绝原因。
  3. 一个Promise内部有三种状态,分别是:pending、fulfilled 和 rejected;一开始状态为pending,可以转换为fulfilled或者rejected其中之一;当状态已为fulfilled状态时,就不能转换为其他状态了,且必须返回一个不能再改变的值value;当状态已为rejected状态时,同样也不能转换为其他状态,且必须返回一个不能再改变的值reason代表原因。
  4. then的链式调用:我们使用Promise时候,每次then执行后必须返回一个新的Promise对象,并且当then函数中返回了任意的值value,我们能再下一个then方法中获取到。
  5. 返回值穿透特性:当我们使用then,没往里传入参数的时候,比如promise1.then().then(),后面执行的then函数依旧可以获得之前then函数返回的值。具体实现如下,更多的细节我会在代码的注释里给出。

一步步实现Promise

  • 实现构造函数。 定义3个常量作为Promise的状态值;初始化status为PENDING状态、value返回值、reason拒绝原因、onResolvedCallbacks储存then方法传入的onFulfilled函数、onRejectedCallbacks储存then方法传入的onRejected函数;再定义个resolve方法为改变promise的状态值并赋值返回值value、执行onResolvedCallbacks数组里的函数;再定义个reject方法为改变promise的状态值并赋值返回值reason、执行onRejectedCallbacks数组里的函数。最后直接在构造函数中执行new Promise里传入的函数,并把定义的resolve和reject函数传入。
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';
class ThePromise {
    constructor(fn) {
        this.status = PENDING;
        this.value = undefined;
        this.reason = undefined;
        this.onResolvedCallbacks = [];
        this.onRejectedCallbacks = [];
        
        const resolve = (value) => {
            if(this.status === PENDING) {
                this.status = FULFILLED;
                this.value = value;
                this.onResolvedCallbacks.forEach(fn => fn());
            }
        }
        
        const reject = () => {
            if(this.status === PENDING) {
                this.status = REJECTED;
                this.reason = reason;
                this.onRejectedCallbacks.forEach(fn => fn());
            }
        }
        
        try {
            fn(resolve, reject);
        }catch(error) {
            reject(error);
        }
    }
}
  • 实现then方法。 then方法需要实现链式调用和返回值穿透特性。实现方案:每次调用then方法的时候,都新建一个promise对象,并把上一次的返回的值传给这个新的promise的then方法里,这样就可以无限链式调用了。更多的细节写在代码的注释里。
then(onResolved, onRejected) {
    // 用于then()的情况下返回值穿透到下一次then
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    // 抛出错误,冒泡出去
    onRejected = typeof onRejected === 'function' ? onRejected : error => { throw error; };
    // 每次调用then都返回一个新的promise,用于链式调用
    const anotherPromise = new ThePromise((resolve, reject) => {
         // 如果promise的状态已经确定并且是FULFILLED,我们调用onResolved,
         // 考虑到有可能throw,所以还需要将其包在try/catch块里
        if(this.status === FULFILLED) {
            setTimeout(() => {
                try {
                    const x = onResolved(this.value);
                    // 递归处理,x可能是一个promise
                    resolvePromise(anotherPromise, x, resolve, reject);
                } catch(error) {
                    reject(error);
                }
            },0);
        }
        // 此处与前一个if块的逻辑几乎相同,区别在于所调用的是onRejected函数
        if(this.status === REJECTED) {
            setTimeout(() => {
                try {
                    const x = onRejected(this.reason);
                    resolvePromise(anotherPromise, x, resolve, reject);
                } catch(error) {
                    reject(error);
                }
            },0);
        }
        // 如果当前的Promise还处于PENDING状态,我们并不能确定调用onResolved还是onRejected,
        // 只能等到Promise的状态确定后,才能确定如何处理
        if(this.status === PENDING) {
            this.onResolvedCallbacks.push(() => {
                try {
                    const x = onResolved(this.value);
                    resolvePromise(anotherPromise, x, resolve, reject);
                }catch(error) {
                    reject(error);
                }
            });
            this.onRejectedCallbacks.push(() => {
                try {
                    const x = onRejected(this.reason);
                    resolvePromise(anotherPromise, x, resolve, reject);
                }catch(error) {
                    reject(error);
                }
            });
        }
    }
    return anotherPromise;
}
function resolvePromise(anotherPromise, x, resolve, reject) {
   // 如果执行回调后的值还是promise自己,则报错
   if(anotherPromise === x) {
       return reject(new TypeError('Wrong implementation'));
   }
   let called; // 用于确定promise的状态只改变一次
   if(x instanceof ThePromise){
       try {
           x.then(f => {
               if(called) return;
               called = true;
               // 递归解析
               resolvePromise(anotherPromise, f, resolve, reject);
           }, r => {
               if(called) return;
               called = true;
               reject(r);
           });
       }catch(error) {
           if(called) return;
           called = true;
           reject(error);
       }
   }else{
       // 如果x不是promise实例则直接resolve
       resolve(x);
   }
}