从零实现一个Promise

从零实现一个Promise

一、Promise/A+ 规范

① Promise 是一个 或者 函数,内部拥有3个状态 ,分别为 pending(等待)、fulfilled(执行、完成)、rejected(拒绝、未完成)

默认为pending状态,即 Promise对象刚创建的时候状态为pending ,并且 pending状态可以转换fulfilled 或者 rejected

fulfilled和rejected为 最终的状态 一旦变为fulfilled或者rejected,那么将无法转变为其他状态

② Promise需要 对外提供一个then方法

promise.then(onFulfilled, onRejected)

如果可选参数onFulfilled和onRejected 不为函数时应该被忽略

onFulfilled和onRejected函数都应该是 异步执行 的;

当调用 onFulfilled 函数时,会将当前 Promise 的值作为参数传入,并且 只能调用一次

当调用 onRejected 函数时,会将当前 Promise 的失败原因作为参数传入,并且 只能调用一次

then函数的返回值仍然为Promise ,以便进行链式调用;

resolvePromise then方法会创建并返回一个Promise对象, then中注册的回调函数会返回各种值,必须进行校验

then方法返回的promise不能与then中回调函数返回值x相等,否则 需要抛出错误

如果是then回调函数返回值为一个 非Promise对象 ,则直接 用then返回的promise对象的resolve方法 ,resolve(x)即可。

如果then回调函数返回值x为一个 Promise对象或者一个带then方法的对象或函数 ,那么需要 执行其then方法注册回调,拿到Promise或类Promise对象的值作为then返回的promise的值 ,如果值仍然为Promise对象则需要进行 递归操作

二、实现Promise

① 根据第一条规范,Promise是一个类或者函数,所以我们先 将Promise定义成一个类 ,同时内部有三个状态,我们将其 定义为常量

var PENDING = "pending"; // 等待状态
var FULFILLED = "fulfilled"; // 执行、完成状态
var REJECTED = "rejected"; // 拒绝、未完成状态
class Promise {
    constructor() {
        this.state = PENDING; // Promise对象创建完成后默认为等待状态

② 我们在创建Promise的时候会 传入一个函数 ,该函数会 在创建Promise对象的时候立即执行 ,并且会 接收两个参数 ,分别用于 执行 拒绝 当前Promise对象,即 修改当前Promise对象的状态 。Promise是用于处理异步的,所以 在Promise状态变为完成的时候可能会接收到异步操作执行的结果,在Promise状态变为未完成的时候可能会接收到失败的原因 ,所以Promise内部还需要保存 异步操作的结果value、失败的原因reason

......
class Promise {
    constructor(executor) { // 传入执行器函数
        ......
        this.value = undefined; // 保存异步操作的结果
        this.reason = undefined; // 保存失败的原因
        const resolve = (value) => {
            this.value = value;
            this.state = FULFILLED; // 将Promise对象的状态改为完成状态
        const reject = (reason) => {
            this.reason = reason;
            this.state = REJECTED; // 将Promise对象的状态改为未完成状态
        try {
            executor(resolve, reject); // 执行器由用户传入可能会发生错误,所以需要进行捕获
        } catch(e) {
            reject(e);

③ 这里还存在一个问题,就是 Promise必须是单次执行的 ,Promise的状态一旦从pending状态修改为fulfilled或者rejected,就不能再发生变化, 从fulfilled变为fulfilled也不可以 ,也就是说 resolve或者reject只能执行一次 。所以我们 需要对resolve和reject内部进行判断 ,如果状态已经变化了则不再执行了,如:

......
class Promise {
    constructor(executor) { // 传入执行器函数
        ......
        const resolve = (value) => {
            if (this.state === PENDING) { // 防止用户多次resolve,以第一次resolve为准
                ......
        const reject = (reason) => {
            if (this.state === PENDING) { // 防止用户多次reject
                ......
        ......

④ 给Promise添加一个then函数, then函数接收onFulfilled, onRejected两个函数作为参数 ,分别用于处理Promise完成时和未完成时的回调函数,如果不是函数,则要进行初始化为一个函数,如:

class Promise {
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => { // 如果onFulfilled不是函数,则初始化一个完成处理函数
            return value;
        onRejected = typeof onRejected === "function" ? onRejected : (reason) => { // 如果onRejected不是函数,则初始化一个未完成处理函数
            throw reason; // 传什么就抛出什么

⑤ then方法其实就是一个 注册回调的过程 ,当调用then的这个Promise对象的状态变为完成状态就可以执行onFulfilled回调函数,当Promise对象的状态变为拒绝状态就可以执行onRejected回调函数了。所以 回调函数的执行依赖于调用then的Promise的状态 。同时为了支持链式调用,then方法还需要返回一个Promise对象。根据前面的Promise规范,传入的回调函数必须异步执行,这里用setTimeout进行模拟。

class Promise {
    then(onFulfilled, onRejected) {
        ......
        let promise;
        switch(this.state) {
            case FULFILLED: // 调用then方法的时候,当前Promise状态已经变成完成状态,则可用立即执行完成的回调函数
                promise = new Promise((resolve, reject) => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value);
                        } catch(e) {
                            console.log(e); // 打印错误信息
                            reject(e);
                break;
            case REJECTED:
                promise = new Promise((resolve, reject) => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason);
                        } catch(e) {
                            reject(e);
                break;
            case PENDING:
                promise = new Promise((resolve, reject) => {
                    // TODO
                break;
        return promise;

⑥ 当调用then的Promise对象处于pending状态的时候,此时通过then注册的回调函数不能立即执行, 必须等待Promise的状态变为最终状态才能执行注册的回调函数 。这里就涉及到了一个 发布订阅模式 。我们可以 先将回调函数保存起来 ,那么什么时候Promise才会变成最终状态呢? 那就是调用resolve或reject的时候 ,所以我们可以 在调用resolve或reject的时候,取出注册的回调函数然后执行 即可。

class Promise {
    constructor(executor) {
        const resolve = (value) => {
            if (this.state === PENDING) { // 防止用户多次resolve,以第一次resolve为准
                ......
                this.onFulfilleds.forEach(fn => fn()); // 取出then中注册的完成回调函数并执行
        const reject = (reason) => {
            if (this.state === PENDING) { // 防止用户多次reject
                ......
                this.onRejecteds.forEach(fn => fn()); // 取出then中注册的拒绝回调函数并执行
    then(onFulfilled, onRejected) {
        ......
        switch(this.state) {
            case PENDING:
                promise = new Promise((resolve, reject) => {
                    this.onFulfilleds.push(() => {
                        try {
                            let x = onFulfilled(this.value);
                        } catch(e) {
                            console.log(e); // 打印错误信息
                            reject(e);
                    this.onRejecteds.push(() => {
                        try {
                            let x = onRejected(this.reason);
                        } catch(e) {
                            reject(e);
                break;

⑦ 接下来就是要 处理then注册的回调函数的返回值 了,因为回调函数的返回值可能是各种各样的情况,可能是普通的值,可能是Promise对象,也可能是带then方法的对象,所以我们要一一进行处理。这里我们使用一个单独的方法resolvePromise()进行各种情况的处理,如:

// 传入then()方法中创建的Promise对象,回调函数的返回值x,then()方法中创建的Promise的resolve、reject
const resolvePromise = function(promise, x, resolve, reject) {
    // TODO
class Promise {
    constructor(executor) { // 传入执行器函数
        ......
    then(onFulfilled, onRejected) {
        case FULFILLED:
            promise = new Promise((resolve, reject) => {
                ......
                let x = onFulfilled(this.value);
                resolvePromise(promise, x, resolve, reject); // 处理回调函数的返回值
        case REJECTED:
            promise = new Promise((resolve, reject) => {
                ......
                let x = onRejected(this.reason);
                resolvePromise(promise, x, resolve, reject); // 处理回调函数的返回值
        case PENDING:
            this.onFulfilleds.push(() => {
                let x = onFulfilled(this.value);
                resolvePromise(promise, x, resolve, reject); // 处理回调函数的返回值
            this.onRejecteds.push(() => {
                let x = onRejected(this.reason);
                resolvePromise(promise, x, resolve, reject); // 处理回调函数的返回值

三、实现resolvePromise

① 如果回调函数返回值与then()方法中创建的Promise对象相同则抛出错误, 这相当于是自己等自己会进入死循环

let p1 = new Promise((resolve, reject) => {
    resolve(1);
let p2 = p1.then((value) => { // p2即then方法内创建Promise对象
    return p2;
// 结果抛出错误,显示Chaining cycle detected for promise #<Promise>
const resolvePromise = function(promise, x, resolve, reject) {
    if (promise === x) { // 禁止resolve自己
        throw new Error("Chaining cycle detected for promise #<Promise>");

② 如果回调函数返回的是一个 Promise对象 或者 带then方法的类Promise对象 ,又或者一个函数,因为 函数上也可能有then方法 ,那么我们需要 取出then方法并执行 ,对于Promise对象而言, then方法的执行就会注册相应的回调函数,等Promise状态变为最终状态后就会执行对应的回调函数 ,回调函数执行后就可以拿到Promise对象的value值,然后 将该value值作为调用then方法创建的Promise的对象的value值

const resolvePromise = function(promise, x, resolve, reject) {
    ......
    if ((x && typeof x === "object") || typeof x === "function") { // 如果是对象或者函数,函数也可能有then方法
        let executed;
        try {
            let then = x.then; // 尝试取出then方法
            if (typeof then === "function") { // 如果该对象上存在then方法,那么是个Promise对象或者包含then方法的对象
                then.call(x, function (y) { // 执行then方法,对于真正的Promise对象,则会注册回调,等到状态变化后,回调函数会执行,回调中能接收到Promise的value值
                    if (executed) return;
                    executed = true; // 注册的回调函数只能执行一次
                    resolvePromise(promise, y, resolve, reject); // 返回值还可能是一个Promise对象,故需要递归直到变为普通值为止
                }, function (e) {
                    if (executed) return;
                    executed = true;
                    reject(e);
            } else { // 不包含then方法的普通对象,直接resolve即可
                resolve(x);      
        } catch(e) {
            if (executed) return;
            executed = true;
            reject(e);
    } else {
        resolve(x);

四、实现catch

catch可以看做是一个 特殊的then方法,其内部会调用then()方法 ,但是 仅注册拒绝的回调函数 ,这也就是then(onFulfilled, onRejected)和then(onFulfilled).catch(onRejected)的区别,如果将onRejected写到then中,那么当 then的onFulfilled发生错误的时候,onRejected就无法捕获到其中的错误 ,而写到catch中,那么就 相当于是下一个then()方法 ,故能捕获到上一个then()方法中发生的错误。

class Promise {
    catch(onRejected) {
        return this.then(null, onRejected); // 仅注册拒绝的回调函数

五、总结

Promise其实就 是一个类 ,内部有state、value、reason等属性,分别用于存储 当前Promise的状态、执行成功后的返回值,执行失败的原因 ,同时内部还提供了resolve、reject两个方法, 这两个方法会以参数的形式传递给执行器 ,即 传递到外部 ,以便修改Promise的状态。

Promise还提供了一个 then方法用于注册回调函数 ,注册回调的时候 与当前Promise的状态有关 ,如果是最终状态,则立即执行,如果是 等待状态 ,则 先保存起来 等到调用resolve或reject方法的时候再取出回调并执行 。注册的回调函数可能会返回各种各样的值:

如果返回的是普通值,那么直接用then返回的Promise的resolve方法resolve即可;

如果返回的是 Promise对象 或者是 带then方法的对象或函数 ,那么需要 调用其then方法并注册一个自定义回调用于接收当前Promise的值 ,等该Promise变为最终状态后会执行回调就可以拿到其value,最后将其作为then返回的Promise的value,即resolve(x)。 完整源码如下:

var PENDING = "pending"; // 等待状态
var FULFILLED = "fulfilled"; // 执行、完成状态
var REJECTED = "rejected"; // 拒绝、未完成状态
// 传入then()方法中创建的Promise对象,回调函数的返回值x,then()方法中创建的Promise的resolve、reject
const resolvePromise = function(promise, x, resolve, reject) {
    if (promise === x) { // 禁止resolve自己
        throw new Error("Chaining cycle detected for promise #<Promise>");
    if ((x && typeof x === "object") || typeof x === "function") { // 如果是对象或者函数,函数也可能有then方法
        let executed;
        try {
            let then = x.then; // 尝试取出then方法
            if (typeof then === "function") { // 如果该对象上存在then方法,那么是个Promise对象或者包含then方法的对象
                then.call(x, function (y) { // 执行then方法,对于真正的Promise对象,则会注册回调,等到状态变化后,回调函数会执行,回调中能接收到Promise的value值
                    if (executed) return;
                    executed = true; // 注册的回调函数只能执行一次
                    resolvePromise(promise, y, resolve, reject); // 返回值还可能是一个Promise对象,故需要递归直到变为普通值为止
                }, function (e) {
                    if (executed) return;
                    executed = true;
                    reject(e);
            } else { // 不包含then方法的普通对象,直接resolve即可
                resolve(x);
        } catch(e) {
            if (executed) return;
            executed = true;
            reject(e);
    } else {
        resolve(x);
class Promise {
    constructor(executor) { // 传入执行器函数
        this.state = PENDING; // Promise对象创建完成后默认为等待状态
        this.value = undefined; // 保存异步操作的结果
        this.reason = undefined; // 保存失败的原因
        this.onFulfilleds = []; // 保存then中注册的完成回调函数
        this.onRejecteds = []; // 保存then中注册的拒绝回调函数
        const resolve = (value) => {
            if (this.state === PENDING) { // 防止用户多次resolve,以第一次resolve为准
                this.value = value;
                this.state = FULFILLED; // 将Promise对象的状态改为完成状态
                this.onFulfilleds.forEach(fn => fn()); // 取出then中注册的完成回调函数并执行
        const reject = (reason) => {
            if (this.state === PENDING) { // 防止用户多次reject
                this.reason = reason;
                this.state = REJECTED; // 将Promise对象的状态改为未完成状态
                this.onRejecteds.forEach(fn => fn()); // 取出then中注册的拒绝回调函数并执行
        try {
            executor(resolve, reject); // 执行器由用户传入可能会发生错误,所以需要进行捕获
        } catch(e) {
            reject(e);
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (value) => { // 如果onFulfilled不是函数,则初始化一个完成处理函数
            return value;
        onRejected = typeof onRejected === "function" ? onRejected : (reason) => { // 如果onRejected不是函数,则初始化一个未完成处理函数
            throw reason; // 传什么就抛出什么
        let promise;
        switch(this.state) {
            case FULFILLED: // 调用then方法的时候,当前Promise状态已经变成完成状态,则可用立即执行完成的回调函数
                promise = new Promise((resolve, reject) => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value);
                            resolvePromise(promise, x, resolve, reject);
                        } catch(e) {
                            console.log(e);
                            reject(e);
                break;
             case REJECTED:
                promise = new Promise((resolve, reject) => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason);
                            resolvePromise(promise, x, resolve, reject);
                        } catch(e) {
                            reject(e);
                break;
            case PENDING:
                promise = new Promise((resolve, reject) => {
                    this.onFulfilleds.push(() => {
                        try {
                            let x = onFulfilled(this.value);
                            resolvePromise(promise, x, resolve, reject);
                        } catch(e) {
                            reject(e);
                    this.onRejecteds.push(() => {
                        try {
                            let x = onRejected(this.reason);
                            resolvePromise(promise, x, resolve, reject);
                        } catch(e) {
                            reject(e);
                break;
        return promise;