js-手撕8

2023-01-01 16:24:47 浏览数 (1)

JS手撕(八) Promise

Promise实现

Promise的原理之前有写过两篇博客,就不细讲了。

但还是需要简单复习一下下。

Promise构造函数的实现

promise的状态一开始是pending,只能从pending变为resolved或从pending变为rejected。并且是不可逆的。

改变promise的状态有三种方法,

  • 调用 resolve 函数:pending => fulfilled(resolved)
  • 调用 reject 函数:pending => rejected
  • 抛出异常:pending => rejected
代码语言:javascript复制
// 1. 初始状态:pending
const p1 = new Promise((resolve, reject) => { });

// 2. 调用resolve函数:pending => fulfilled(resolved)
const p2 = new Promise((resolve, reject) => resolve(123));

// 3. 调用reject函数:pending => rejected
const p3 = new Promise((resolve, reject) => reject('error'));

// 4. 抛出异常:pending => rejected
const p4 = new Promise((resolve, reject) => {
  throw new Error('异常');
});


console.log(p1);
console.log(p2);
console.log(p3);
console.log(p4);

从上图可以看出,promise对象还会有PromiseStatePromiseResult属性。

  • PromiseState:对象的状态
  • PromiseResult:对象结果值
代码语言:javascript复制
class MyPromise {
  // Promise对象的状态
  PromiseState = 'pending';

  // Promise对象的结果
  PromiseResult = undefined;

  constructor(executor) { // 执行器函数executor

    // 需要有`resolve`和`reject`函数,因为实例化Promise对象时需要传参`(resolve, reject) => { }`形式的函数
    const resolve = (data) => {
      if (this.PromiseState === 'pending') {
        // 只能从`pending`状态变为`resolved`
        this.PromiseState = 'fulfilled';
        this.PromiseResult = data;
      }
    };

    const reject = (data) => {
      if (this.PromiseState === 'pending') {
        // 只能从`pending`状态变为`rejected`
        this.PromiseState = 'rejected';
        this.PromiseResult = data;
      }
    };

    executor(resolve, reject);
  }
}

前3种情况已经能够实现了

代码语言:javascript复制
// 1. 初始状态:pending
const p1 = new MyPromise((resolve, reject) => { });

// 2. 调用resolve函数:pending => fulfilled(resolved)
const p2 = new MyPromise((resolve, reject) => resolve(123));

// 3. 调用reject函数:pending => rejected
const p3 = new MyPromise((resolve, reject) => reject('error'));

console.log(p1);
console.log(p2);
console.log(p3);

但是,抛出异常的情况还不能实现,因为异常没有被捕获,所以会直接报错,后面的代码也不能执行。所以要我们在调用执行器函数executor时,应该添加try catch,如果被捕获,那就要执行reject方法。

代码语言:javascript复制
try {
  executor(resolve, reject);
} catch (err) {
  reject(err);
}

then方法的实现

首先,简单的实现一下

代码语言:javascript复制
then(onResolved, onRejected) {
  if (this.PromiseState === "fulfilled") {
    onResolved(this.PromiseResult);
  } else if (this.PromiseState === "rejected") {
    onRejected(this.PromiseResult);
  }
}

代码语言:javascript复制
// 1. 初始状态:pending
const p1 = new MyPromise((resolve, reject) => { });

// 2. 调用resolve函数:pending => fulfilled(resolved)
const p2 = new MyPromise((resolve, reject) => resolve(123));

// 3. 调用reject函数:pending => rejected
const p3 = new MyPromise((resolve, reject) => reject('error'));

// 4. 抛出异常:pending => rejected
const p4 = new MyPromise((resolve, reject) => {
  throw new Error('异常');
});


p1.then((data) => {
  console.log(data);
});

p2.then((data) => {
  console.log(data);
});

p3.then(null, (reason) => {
  console.error(reason);
});

p4.then(null, (reason) => {
  console.error(reason);
});

完美(×),使用Promise难免遇到异步任务,所以还需要测试一下。

代码语言:javascript复制
const p5 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('123456');
  }, 0)
})

p5.then((data) => {
  console.log(data);
})

没反应。这是因为因为是异步任务,所以执行then方法时,状态还是pending,所以还需要定义数据结构来存回调函数,如果是异步任务,即状态是pending时,存好回调函数。

代码语言:javascript复制
then(onResolved, onRejected) {
  if (this.PromiseState === "fulfilled") {
    onResolved(this.PromiseResult);
  } else if (this.PromiseState === "rejected") {
    onRejected(this.PromiseResult);
  } else if (this.PromiseState === 'pending') {
    this.resolvedQueue.push(onResolved);
    this.rejectedQueue.push(onRejected);
  }
}

存好回调函数,自然还是需要调用的。所以resolve函数和reject函数最后还需要遍历执行对应回调队列。

代码语言:javascript复制
const resolve = (data) => {
  if (this.PromiseState === 'pending') {
    // 只能从`pending`状态变为`resolved`
    this.PromiseState = 'fulfilled';
    this.PromiseResult = data;

    this.resolvedQueue.forEach(callback => callback(data));
  }
};

reject函数同理

这样子就能处理异步任务了。

但是,这个时候Promise.then()方法并不是异步任务。

代码语言:javascript复制
const p5 = new MyPromise((resolve, reject) => {
  resolve('123456');
  console.log(88888);
})

p5.then((res) => {
  console.log(res)
})

console.log(99999);

根据JavaScript的执行机制,应该先执行完同步任务,再执行异步任务,并且Promise.then()是异步任务(微任务),所以应该依次输出8888899999123456才对。

所以,then方法还得改一下,需要添加定时器来让它变成异步任务。

代码语言:javascript复制
then(onResolved, onRejected) {
  if (this.PromiseState === "fulfilled") {
    setTimeout(() => {
      onResolved(this.PromiseResult);
    })

  } else if (this.PromiseState === "rejected") {
    setTimeout(() => {
      onRejected(this.PromiseResult);
    })

  } else if (this.PromiseState === 'pending') {
    this.resolvedQueue.push(onResolved);
    this.rejectedQueue.push(onRejected);
  }
}

小优化:将then中的参数变为可选。实际上原生Promise.then()方法的参数不传参数或者只传一个参数都不会影响执行。

原理很简单,只需要在then方法最前面判断参数是不是函数,不是则把它变成函数即可。

代码语言:javascript复制
if (typeof onRejected !== "function") {
  onRejected = (reason) => {
    throw reason;
  };
}

if (typeof onResolved !== "function") {
  onResolved = (value) => value;
}

then()方法返回结果,实现链式调用。原理就是返回一个新的Promise对象。当然不能只是返回一个Promise对象就行了,还需要判断回调得到的结果是不是Promise对象,如果是,还得调用then()方法,将状态变为fulfilled 或者rejected,并变更结果为then()方法返回的值。

如:

代码语言:javascript复制
const p5 = new Promise((resolve, reject) => {
  resolve('123456');
})

const p6 = p5.then(() => {
  return 123567;
});

const p7 = p6.then(() => {
  return new Promise((resolve, reject) => {
    resolve('success');
  })
})

console.log(p6);
console.log(p7);

代码语言:javascript复制
then(onResolved, onRejected) {
  if (typeof onRejected !== "function") {
    onRejected = (reason) => {
      throw reason;
    };
  }

  if (typeof onResolved !== "function") {
    onResolved = (value) => value;
  }

  // 以下部分代码微调
  return new MyPromise((resolve, reject) => {
    if (this.PromiseState === "fulfilled") {
      let result;

      setTimeout(() => {
        result = onResolved(this.PromiseResult);

        // resolvePromise判断回调得到的结果是不是MyPromise,是的话会继续执行then方法
        resolvePromise(result, resolve, reject);
      })

    } else if (this.PromiseState === "rejected") {
      setTimeout(() => {
        result = onRejected(this.PromiseResult);
        resolvePromise(result, resolve, reject);
      })

    } else if (this.PromiseState === 'pending') {
      this.resolvedQueue.push((() => {
        resolvePromise(onResolved(this.PromiseResult), resolve, reject)
      }));
      this.rejectedQueue.push(() => {
        resolvePromise(onResolved(this.PromiseResult), resolve, reject)
      });
    }
  })
}

resolvePromise

代码语言:javascript复制
function resolvePromise(x, resolve, reject) {
  if (x instanceof MyPromise) {

    // 如果是`MyPromise`对象,则需要调用thrn方法,将状态变为 fulfilled 或者 rejecte
    x.then(resolve, reject);
  } else {
    // 普通值直接resolve就行
    resolve(x);
  }
}

测试:

代码语言:javascript复制
const p5 = new MyPromise((resolve, reject) => {
  resolve('123456');
})

const p6 = p5.then(() => {
  return 123567;
});

const p7 = p6.then(() => {
  return new MyPromise((resolve, reject) => {
    resolve('success');
  })
})

console.log(p6);
console.log(p7);

catch方法的实现

在搞完上面的then方法后,catch方法就迎刃而解了。因为catch实际上就是一个语法糖,我们也可以用then方法来实现,只需要不传第一个参数,只传第二个参数即可。

代码语言:javascript复制
catch(onRejected) {
  return this.then(undefined, onRejected);
}

测试:

代码语言:javascript复制
new MyPromise((resolve, reject) => {
  reject('error');
})
  .catch((reason) => {
    console.error(reason);
  })

完整代码

代码语言:javascript复制
class MyPromise {
  constructor(executor) { // 执行器函数executor
    // Promise对象的状态
    this.PromiseState = 'pending';

    // Promise对象的结果
    this.PromiseResult = undefined;

    this.resolvedQueue = [];
    this.rejectedQueue = [];

    // 需要有`resolve`和`reject`函数,因为实例化Promise对象时需要传参`(resolve, reject) => { }`形式的函数
    const resolve = (data) => {
      if (this.PromiseState === 'pending') {
        // 只能从`pending`状态变为`resolved`
        this.PromiseState = 'fulfilled';
        this.PromiseResult = data;

        this.resolvedQueue.forEach(callback => callback(data));
      }
    };

    const reject = (data) => {
      if (this.PromiseState === 'pending') {
        // 只能从`pending`状态变为`rejected`
        this.PromiseState = 'rejected';
        this.PromiseResult = data;

        this.rejectedQueue.forEach(callback => callback(data));
      }
    };

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  then(onResolved, onRejected) {
    if (typeof onRejected !== "function") {
      onRejected = (reason) => {
        throw reason;
      };
    }

    if (typeof onResolved !== "function") {
      onResolved = (value) => value;
    }

    return new MyPromise((resolve, reject) => {
      if (this.PromiseState === "fulfilled") {
        let result;

        setTimeout(() => {
          result = onResolved(this.PromiseResult);
          resolvePromise(result, resolve, reject);
        })

      } else if (this.PromiseState === "rejected") {
        setTimeout(() => {
          result = onRejected(this.PromiseResult);
          resolvePromise(result, resolve, reject);
        })

      } else if (this.PromiseState === 'pending') {
        this.resolvedQueue.push((() => {
          resolvePromise(onResolved(this.PromiseResult), resolve, reject)
        }));
        this.rejectedQueue.push(() => {
          resolvePromise(onResolved(this.PromiseResult), resolve, reject)
        });
      }
    })
  }

  catch(onRejected) {
    return this.then(undefined, onRejected);
  }
}

function resolvePromise(x, resolve, reject) {
  if (x instanceof MyPromise) {

    // 如果是`MyPromise`对象,则需要调用then方法,将状态变为 fulfilled 或者 rejecte
    x.then(resolve, reject);
  } else {
    // 普通值直接resolve就行
    resolve(x);
  }
}

参考

  • Promise学习笔记(二) | 赤蓝紫
  • 从一道让我失眠的 Promise 面试题开始,深入分析 Promise 实现细节 - 掘金

0 人点赞