> 文章列表 > js:手写一个promise

js:手写一个promise

js:手写一个promise

背景

promise 作为前端开发中常用的函数,解决了 js 处理异步时回调地狱的问题,大家应该也不陌生了,今天来学习一下 promise 的实现过程,这样可以加(面)深(试)理(要)解(考)。

需求

我们先来总结一下 promise 的特性:
使用:

const p1 = new Promise((resolve, reject) => {console.log('1');resolve('成功了');
})console.log("2");const p2 = p1.then(data => {console.log('3')throw new Error('失败了')
})const p3 = p2.then(data => {console.log('success', data)
}, err => {console.log('4', err)
})

js:手写一个promise
js:手写一个promise
以上的示例可以看出 promise 的一些特点,也就是我们本次要做的事情:

  • 在调用 Promise 时,会返回一个 Promise 对象,包含了一些熟悉和方法(all/resolve/reject/then…)
  • 构建 Promise 对象时,需要传入一个 executor 函数,接受两个参数,分别是resolve和reject,Promise 的主要业务流程都在 executor 函数中执行。
  • 如果运行在 excutor 函数中的业务执行成功了,会调用 resolve 函数;如果执行失败了,则调用 reject 函数。
  • promise 有三个状态:pending,fulfilled,rejected,默认是 pending。只能从pending到rejected, 或者从pending到fulfilled,状态一旦确认,就不会再改变;
  • promise 有一个then方法,接收两个参数,分别是成功的回调 onFulfilled, 和失败的回调 onRejected。
// 三个状态:PENDING、FULFILLED、REJECTED
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";class Promise {constructor(executor) {// 默认状态为 PENDINGthis.status = PENDING;// 存放成功状态的值,默认为 undefinedthis.value = undefined;// 存放失败状态的值,默认为 undefinedthis.reason = undefined;// 调用此方法就是成功let resolve = (value) => {// 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法if (this.status === PENDING) {this.status = FULFILLED;this.value = value;}};// 调用此方法就是失败let reject = (reason) => {// 状态为 PENDING 时才可以更新状态,防止 executor 中调用了两次 resovle/reject 方法if (this.status === PENDING) {this.status = REJECTED;this.reason = reason;}};try {// 立即执行,将 resolve 和 reject 函数传给使用者executor(resolve, reject);} catch (error) {// 发生异常时执行失败逻辑reject(error);}}// 包含一个 then 方法,并接收两个参数 onFulfilled、onRejectedthen(onFulfilled, onRejected) {if (this.status === FULFILLED) {onFulfilled(this.value);}if (this.status === REJECTED) {onRejected(this.reason);}}
}

调用一下:

const promise = new Promise((resolve, reject) => {resolve('成功');
}).then((data) => {console.log('success', data)},(err) => {console.log('faild', err)}
)

js:手写一个promise
这个时候我们很开心,但是别开心的太早,promise 是为了处理异步任务的,我们来试试异步任务好不好使:

const promise = new Promise((resolve, reject) => {// 传入一个异步操作setTimeout(() => {resolve('成功');},1000);
}).then((data) => {console.log('success', data)},(err) => {console.log('faild', err)}
)

发现没有任何输出,我们来分析一下为什么:
new Promise 执行的时候,这时候异步任务开始了,接下来直接执行 .then 函数,then函数里的状态目前还是 padding,所以就什么也没执行。

那么我们想想应该怎么改呢?
我们想要做的就是,当异步函数执行完后,也就是触发 resolve 的时候,再去触发 .then 函数执行并把 resolve 的参数传给 then。

这就是典型的发布订阅的模式,可以看我 这篇 文章。

const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';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) {if (this.status === FULFILLED) {onFulfilled(this.value)}if (this.status === REJECTED) {onRejected(this.reason)}if (this.status === PENDING) {// 如果promise的状态是 pending,需要将 onFulfilled 和 onRejected 函数存放起来,等待状态确定后,再依次将对应的函数执行this.onResolvedCallbacks.push(() => {onFulfilled(this.value)});// 如果promise的状态是 pending,需要将 onFulfilled 和 onRejected 函数存放起来,等待状态确定后,再依次将对应的函数执行this.onRejectedCallbacks.push(()=> {onRejected(this.reason);})}}
}

这下再进行测试:

const promise = new Promise((resolve, reject) => {setTimeout(() => {resolve('成功');},1000);
}).then((data) => {console.log('success', data)},(err) => {console.log('faild', err)}
)

1s 后输出了:
js:手写一个promise
说明成功啦

then的链式调用

这里就不分析细节了,大体思路就是每次 .then 的时候重新创建一个 promise 对象并返回 promise,这样下一个 then 就能拿到前一个 then 返回的 promise 了。

const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';const resolvePromise = (promise2, x, resolve, reject) => {// 自己等待自己完成是错误的实现,用一个类型错误,结束掉 promise  Promise/A+ 2.3.1if (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.1let then = x.then;if (typeof then === 'function') { // 不要写成 x.then,直接 then.call 就可以了 因为 x.then 会再次取值,Object.defineProperty  Promise/A+ 2.3.3.3then.call(x, y => { // 根据 promise 的状态决定是成功还是失败if (called) return;called = true;// 递归解析的过程(因为可能 promise 中还有 promise) Promise/A+ 2.3.3.3.1resolvePromise(promise2, y, resolve, reject); }, r => {// 只要失败就失败 Promise/A+ 2.3.3.3.2if (called) return;called = true;reject(r);});} else {// 如果 x.then 是个普通值就直接返回 resolve 作为结果  Promise/A+ 2.3.3.4resolve(x);}} catch (e) {// Promise/A+ 2.3.3.2if (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.4onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;//因为错误的值要让后面访问到,所以这里也要跑出个错误,不然会在之后 then 的 resolve 中捕获onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };// 每次调用 then 都返回一个新的 promise  Promise/A+ 2.2.7let promise2 = new Promise((resolve, reject) => {if (this.status === FULFILLED) {//Promise/A+ 2.2.2//Promise/A+ 2.2.4 --- setTimeoutsetTimeout(() => {try {//Promise/A+ 2.2.7.1let x = onFulfilled(this.value);// x可能是一个proimiseresolvePromise(promise2, x, resolve, reject);} catch (e) {//Promise/A+ 2.2.7.2reject(e)}}, 0);}if (this.status === REJECTED) {//Promise/A+ 2.2.3setTimeout(() => {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;}
}

Promise.all

核心就是有一个失败则失败,全成功才进行 resolve

Promise.all = function(values) {if (!Array.isArray(values)) {const type = typeof values;return new TypeError(`TypeError: ${type} ${values} is not iterable`)}return new Promise((resolve, reject) => {let resultArr = [];let orderIndex = 0;const processResultByKey = (value, index) => {resultArr[index] = value;if (++orderIndex === values.length) {resolve(resultArr)}}for (let i = 0; i < values.length; i++) {let value = values[i];if (value && typeof value.then === 'function') {value.then((value) => {processResultByKey(value, i);}, reject);} else {processResultByKey(value, i);}}});
}