1- 注释版代码
/**
* 在开始实现Promise之前 我们需要了解一些Promise的基本标准(作者的个人总结,权威详情可以去官方的promiseA+规范了解)
* 1- Promise 是一个构造函数 可以通过 new 关键字 获取该Promise对象的信息
* 2- Promise 存在三个状态 默认为pending(等待状态) fulfilled(承诺状态) rejected(拒绝状态)
* 3- Promise 状态只允许更改一次 一旦改变不再接收更改
* 4- Promise 构造函数 接收一个回调函数(executor) executor函数又接收两个回调入参 resolve成功回调 和 reject失败回调
* 5- Promise 的实例 存在两个方法可以调用 then 和 catch
* 6- then方法 可以实现链式调用 如 Promise实例.then().then().then()......
* 7- then方法 接收两个回调函数入参 第一个回调获取 fulfilled(承诺状态)返回的值 第二个回调获取 rejected(拒绝状态)返回的拒绝原因
* 8- then方法 两个入参可以选择是否传入
* 9- new Promise executor方法是同步执行
* 10- 实例.then() 方法执行 是微任务
* */
/**
* 实现Promise注意事项
* 1- then为链式调用 因此then方法需要返回一个新的Promise对象
* 2- then方法是同步执行
* 3- then方法 与 resolve成功回调和reject失败回调 的执行关系为异步,因此会涉及到 订阅发布模式
* 4- then方法 会返回一个新的Promise实例 才能满足then的链式调用
* */
function MyPromise(executor) {
/**
* 定义Promise实例的基本数据
* 1- state 实例的状态 对应文章开始的Promise的基本标准2
* 2- value Promise执行结束的值
* 3- onFulfilledCallbacks 订阅数组,保存then方法的第一个入参回调函数 对应文章开始的Promise的基本标准7 注意事项3
* 4- onRejectedCallbacks 订阅数组,保存then方法的第二个入参回调函数 对应文章开始的Promise的基本标准7 注意事项3
* */
this.state = 'pending';
this.value = null;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
/**
* 定义resolve函数 以回调使用
* Promise构造函数接收的回调函数入参 所接收的两个回调函数入参 resolve和reject 对应文章开始的Promise的基本标准4
* 只在promise实例状态为pending时执行 对应文章开始的Promise的基本标准3
* 功能处理1 改变实例状态 pending->fulfilled
* 功能处理2 将处理成功的值保存在实例的value属性里
* 功能处理3 发布数组 执行所有保存then方法的第一个回调函数 将value返回出去 对应文章开始的Promise的基本标准7 注意事项3
* */
this.resolve = (res) => {
if (this.state === 'pending') {
setTimeout(() => {
this.state = 'fulfilled';
this.value = res;
this.onFulfilledCallbacks.forEach(callback => {
callback(this.value)
})
});
}
}
/**
* 定义resolve函数 以回调使用
* Promise构造函数接收的回调函数入参 所接收的两个回调函数入参 resolve和reject 对应文章开始的Promise的基本标准4
* 只在promise实例状态为pending时执行 对应文章开始的Promise的基本标准3
* 功能处理1 改变实例状态 pending->rejected
* 功能处理2 将拒绝的原因(错误原因)保存在实例的value属性里
* 功能处理3 发布数组 执行所有保存then方法的第二个回调函数 将value返回出去 对应文章开始的Promise的基本标准7 注意事项3
* */
this.reject = (err) => {
if (this.state === 'pending') {
setTimeout(() => {
this.state = 'rejected';
this.value = err;
this.onRejectedCallbacks.forEach(callback => {
callback(this.value)
})
});
}
}
/**
* 执行Promise构造函数接收的回调函数入参
* 用户的代码执行可能会存在错误 这里使用try-catch,没问题则执行,有问题则通过reject回调函数返回错误原因
* */
try {
executor(this.resolve, this.reject);
} catch (e) {
this.reject(e)
}
}
/**
* catch的实现并不难,相当与then第一个参数为null的情况
* 例子请参照 案例3和案例4
*/
MyPromise.prototype.catch = function (error) {
return this.then(null, error);
}
MyPromise.prototype.then = function (res, err) {
/**
* res, err 两个入参 用户可以选择是否传入 因此需要兼容未传入的情况 对应文章开始的Promise的基本标准8
* res 用户没有传入的情况下 需要兼容成一个回调函数 配合 resolve 将实例value 返回给用户使用
* err 用户没有传入的情况下 需要兼容成一个回调函数 配合 reject 将失败原因 返回给用户使用
* */
res = typeof res === 'function' ? res : value => value;
err = typeof err === 'function' ? err : reason => {
throw reason;
};
/**
* 新建一个Promise对象 并返回它的实例 用于链式调用 对应实现Promise注意事项1
* */
const promise = new MyPromise((resolve, reject) => {
/**
* 执行到这 开始执行对promise实例对象的处理 通过判断promise实例的状态 state 选择对应的处理
* */
if (this.state === 'fulfilled') {
/**
* 1- promise实例的状态为fulfilled 代表promise执行承诺状态 调用了resolve
* 2- 当then(a,b)a函数里 用户可能会return一些东西 也许是原始值,引用值, 比如一个新的Promise
* 3- 为了能够兼容处理用户所return的各种可能性 我们需要进行再一次深入的判断
* 4- 所以这里调用了一个自己定义的函数方法 resolvePromise 进行统一处理
* 5- resolvePromise接受4个入参
* 6- then()返回的新promise实例(后续在resolvePromise定义的地方会解释 为什么需要传入这个对象);
* 7- x 也就是res(this.value)的结果 即此注释的第2点 resolvePromise的存在就是为了处理 x 这个值
* 8- then()所返回的promise依然存在两个回调入参 第一个入参 resolve 负责将resolvePromise处理 x 的结果 返回给用户使用(该判断使用第一个入参 resolve功能)
* 9- then()所返回的promise依然存在两个回调入参 第二个入参 reject 负责将resolvePromise处理 x 的所产生的错误 返回给用户使用
* 10- setTimeout存在意义, resolvePromise 的第一个入参 便是当前的promise实例,而此时promise尚未执行完毕,便无法将它作为入参使用
* 因此需要套上一个setTimeout,变成宏任务,等待当前执行栈代码执行完毕以后,才会执行此宏任务,届时promise已执行完毕,可当做入参使用
* */
setTimeout(() => {
try {
let x = res(this.value);
this.resolvePromise(promise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
} else if (this.state === 'rejected') {
/**
* rejected 状态与上面fulfilled 执行机制同理 只不过不执行第8条注释 而是执行第9条注释
* */
setTimeout(() => {
try {
let x = err(this.value);
this.resolvePromise(promise, x, resolve, reject);
} catch (e) {
reject(e)
}
});
} else if (this.state === 'pending') {
/**
* 之前我们说过 then()方法和[resolve()-reject()]方法 是异步执行
* 比如案例1 then会先执行(可在then定义的第一行debugger调试查看)
* 这种情况 如果then里面直接获取实例的value,那肯定是undefined
* 因为resolve('成功')尚未执行 成功 这个值尚未赋值给实例的value
* 因此我们需要先将处理函数push进数组保存起来,待resolve('成功')执行后 再调用
* 这就是发布订阅模式 对应实现Promise注意事项3
* */
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
let x = res(this.value);
this.resolvePromise(promise, x, resolve, reject)
} catch (e) {
reject(e);
}
});
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = err(this.value);
this.resolvePromise(promise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
})
return promise
}
/**
* 定义resolvePromise 作用是处理 then(cb) cb返回的值
*/
MyPromise.prototype.resolvePromise = function (promise, x, resolve, reject) {
/**
* 第一个参数promise 的作用 衔接上面入参详解
* 判断promise 和 then(cb) cb返回的值 是否为同一个,避免死循环 promiseA+规范有规定
* 如果相同 则停止该函数 并返回一个错误信息
* 如案例2
*/
if (x === promise) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
/**
* 判断x 是否为promise
*/
if (x instanceof MyPromise) {
if (x.state === 'pending') {
/**
* x的状态为pending,说明实例的状态和值没有改变
* 这个时候不能去获取 x 实例的值 因此我们沿用之前的发布订阅模式,走一边then函数
* 把then里面的两个回调函数 保存入数组 当实例对应的resolve或者rejectz执行时
* 自然会执行我们所保存的函数 将实例的value放入y,然后再走一遍resolvePromise,进行对y值的判断和处理
*/
x.then(y => {
this.resolvePromise(promise, y, resolve, reject)
}, reject);
} else if (x.state === 'fulfilled') {
/**
* x的状态如果不为pending,说明实例的状态和值已经改变
* 可以直接根据状态 进行resolve 和 reject处理
*/
resolve(x.value);
} else if (x.state === 'rejected') {
/**
* x的状态如果不为pending,说明实例的状态和值已经改变
* 可以直接根据状态 进行resolve 和 reject处理
*/
reject(x.value);
}
} else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
/**
* x 不是promise 但可能是一个函数或者普通对象
* 关于try-catch就不多说 本文的try-catch功能存在的作用都一致
*/
try {
var then = x.then;
} catch (e) {
return reject(e);
}
/**
* 这里开始 可能会有人疑惑了 为什么还需要进行这么多的判断和控制 直接resolve或者reject不好吗 判断then的意义是什么
* 因为这里还需要判断x是否是一个promise 听起来是不是很懵 之前不是 (x instanceof MyPromise) 判断过了吗
* 很可惜的说 (x instanceof MyPromise) 不能判断所有promise
* 因为有些库也有自己写的promise, 比如我们现在这个写的叫做 MyPromise
* 那怎么判断就没有其他的promise呢 比如 YourPromise HisPromise 等等......
* 那怎么办 只能凉拌了!
* 只能把拥有then方法的对象(所谓的thenable对象)都当成是一个promise执行了......
* 所以x.then 如果真的存在,且是一个函数,那就执行它,并且把this指向转移 x
* 关于then的执行和两个y,r入参 就不多说了 与上面描述的是一致的
* 需要注意的是 called 的作用
* promiseA+规范说明了 如果new Promise的过程中 用户同时使用 resolve和reject
* 那么只能执行一个 called 的作用就是上一个锁,resolve和reject一旦某个执行 变锁死不会执行另一个方法
*/
if (typeof then === 'function') {
let called = false;
try {
then.call(
x,
y => {
if (called) return;
called = true;
this.resolvePromise(promise, y, resolve, reject);
},
r => {
if (called) return;
called = true;
reject(r);
}
)
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
} else {
/**
* 说明 x 为基本数据类型 可以直接resolve(x)
*/
resolve(x);
}
}
/**
* 案例1
* resolve 在延时一秒后才会改变 then会先执行
* */
// const s = new MyPromise((resolve, reject) => {
// setTimeout(() => {
// resolve('成功');
// }, 1000)
// })
// s.then(res => {
// console.log(res);//undefined
// })
/**
* 案例2
* */
// const s = new MyPromise((resolve, reject) => {
// setTimeout(() => {
// resolve('成功');
// }, 1000)
// })
// const p = s.then(res => {
// return p;
// })
/**
* 案例3
* */
// const s = new MyPromise((resolve, reject) => {
// resolve(100);
// throw Error('错误报错')
// })
// s.then(null, err => {
// console.log('失败', err)
// })
/**
* 案例4
* */
// const s1 = new MyPromise((resolve, reject) => {
// resolve(100);
// throw Error('错误报错')
// })
// s1.then().catch(err => {
// console.log('失败', err)
// })
2-纯净版代码
function MyPromise(executor) {
this.state = 'pending';
this.value = null;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
this.resolve = (res) => {
if (this.state === 'pending') {
setTimeout(() => {
this.state = 'fulfilled';
this.value = res;
this.onFulfilledCallbacks.forEach(callback => {
callback(this.value)
})
});
}
}
this.reject = (err) => {
if (this.state === 'pending') {
setTimeout(() => {
this.state = 'rejected';
this.value = err;
this.onRejectedCallbacks.forEach(callback => {
callback(this.value)
})
});
}
}
try {
executor(this.resolve, this.reject);
} catch (e) {
this.reject(e)
}
}
MyPromise.prototype.catch = function (error) {
return this.then(null, error);
}
MyPromise.prototype.then = function (res, err) {
res = typeof res === 'function' ? res : value => value;
err = typeof err === 'function' ? err : reason => {
throw reason;
};
const promise = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
let x = res(this.value);
this.resolvePromise(promise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
} else if (this.state === 'rejected') {
setTimeout(() => {
try {
let x = err(this.value);
this.resolvePromise(promise, x, resolve, reject);
} catch (e) {
reject(e)
}
});
} else if (this.state === 'pending') {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
let x = res(this.value);
this.resolvePromise(promise, x, resolve, reject)
} catch (e) {
reject(e);
}
});
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = err(this.value);
this.resolvePromise(promise, x, resolve, reject);
} catch (e) {
reject(e);
}
});
});
}
})
return promise
}
MyPromise.prototype.resolvePromise = function (promise, x, resolve, reject) {
if (x === promise) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
if (x instanceof MyPromise) {
if (x.state === 'pending') {
x.then(y => {
this.resolvePromise(promise, y, resolve, reject)
}, reject);
} else if (x.state === 'fulfilled') {
resolve(x.value);
} else if (x.state === 'rejected') {
reject(x.value);
}
} else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
var then = x.then;
} catch (e) {
return reject(e);
}
if (typeof then === 'function') {
let called = false;
try {
then.call(
x,
y => {
if (called) return;
called = true;
this.resolvePromise(promise, y, resolve, reject);
},
r => {
if (called) return;
called = true;
reject(r);
}
)
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
} else {
resolve(x);
}
}
/**
* 案例1
* resolve 在延时一秒后才会改变 then会先执行
* */
// const s = new MyPromise((resolve, reject) => {
// setTimeout(() => {
// resolve('成功');
// }, 1000)
// })
// s.then(res => {
// console.log(res);//undefined
// })
/**
* 案例2
* */
// const s = new MyPromise((resolve, reject) => {
// setTimeout(() => {
// resolve('成功');
// }, 1000)
// })
// const p = s.then(res => {
// return p;
// })
/**
* 案例3
* */
// const s = new MyPromise((resolve, reject) => {
// resolve(100);
// throw Error('错误报错')
// })
// s.then(null, err => {
// console.log('失败', err)
// })
/**
* 案例4
* */
// const s1 = new MyPromise((resolve, reject) => {
// resolve(100);
// throw Error('错误报错')
// })
// s1.then().catch(err => {
// console.log('失败', err)
// })
3-测试实现结果
相信以上的代码和注释已经十分详细了,接下来跑一下官方的测试用例。
1-npm install promises-aplus-tests -D
2-根目录下新建一个MyPromise.js文件,把我们写好的代码直接丢进去,并在末尾进行配置并导出
MyPromise.deferred = function () {
var result = {};
result.promise = new MyPromise(function (resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});
return result;
}
module.exports = MyPromise;
3-
根目录下新建一个package.json文件,配置一下启动项
{
"devDependencies": {
"promises-aplus-tests": "^2.1.2"
},
"scripts": {
"start": "promises-aplus-tests MyPromise"
}
}
4-
直接启动:npm run start
完美兼容官方的测试用例,至此,码完收工!