【面试题解析】手动实现Promise
前台面试的时候,经常能看到这样一道题,实现一个Promise。
这篇文章将一步步实现 Promise,彻底弄懂 Promise。
Promise 基本构成
平常使用 Promise 我们可以知道 Promise 存在三种状态 Pending、Resolve、Reject,在 new Promise 时需要传入一个函数, 参数为 resolve 和 reject 的函数,这两个函数用来改变 Promise 的状态。
最重要的还有个 then 的方法,then 函数可以传入两个函数作为参数,第一个函数用来获取异步操作的结果,第二个函数用来获取错误的起因。
除此之外还需要 value 和 reason 存放 Promise 的结果或者错误起因。
从上面这些信息可以转化为下面的代码:
const PENDING = 'pending';const RESOLVED = 'resolved';const REJECTED = 'rejected';class Promise { constructor(executor) { this.status = PENDING; this.value = null; this.reason = null; function resolve (value) { this.status = RESOLVED; this.value = value; }; function reject (reason) { this.status = REJECTED; this.reason = reason; }; executor(resolve.bind(this), reject.bind(this)); } then(onFulfilled, onRejected) { if (this.status === RESOLVED) { onFulfilled(this.value); } if (this.status === REJECTED) { onRejected(this.reason); } }}Promise 的状态只允许修改一次,那么 resolve 和 reject 需要加上状态判断。
function resolve (value) { if (this.status !== PENDING) return; this.status = RESOLVED; this.value = value;};function reject (reason) { if (this.status !== PENDING) return; this.status = REJECTED; this.reason = reason;};在调用 then 函数时,Promise 的状态有可能还是 Pending 的状态,这时需要将 then 函数的两个参数进行保存,状态改变时在进行调用。then 函数有可能会调用屡次,那么可以用数组保存参数。
class Promise { constructor(executor) { // ... this.resolveCbs = []; this.rejectCbs = []; function resolve (value) { // ... this.resolveCbs.map(fn => fn(this.value)); }; function reject (reason) { // ... this.rejectCbs.map(fn => fn(this.reason)); }; } then(onFulfilled, onRejected) { // ... if (this.status === PENDING) { this.resolveCbs.push(onFulfilled); this.rejectCbs.push(onRejected); } }}写到这里,一个最基本的 Promise 即可以使用了。
new Promise((resolve, reject) => { setTimeout(() => { resolve(1); }, 500);}).then(res => { console.log(res);});上面的代码尽管完成了最基本的 Promise,但是还未实现 then 函数的链式调用。
实现链式调用
new Promise((resolve, reject) => { // ...}).then(res => { // ...}).then(res => { // ...})链式调用也是 Promise 的重点所在,由于有了链式调用,才能避免回调地狱的问题。接下来就来一步步实现。
then 是 Promise 的方法,为了能够继续调用 then 函数,需要 then 函数返回一个新的 Promise。
onFulfilled 或者 onRejected 的返回值有可能也是一个 Promise,那么需要等待 Promise 执行完的结果传递给下一个 then 函数。假如返回的不是 Promise,即可以将结果传递给下一个 then 函数。
将 then 函数进行如下修改,resolvePromise 另外实现。
class Promise { // ... then(onFulfilled, onRejected) { let promise2 = new Promise((resolve, reject) => { if (this.status === RESOLVED) { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); } if (this.status === REJECTED) { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); } if (this.status === PENDING) { this.resolveCbs.push(() => { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); }); this.rejectCbs.push(() => { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); }); } }); return promise2; }}实现 resolvePromise
then(onFulfilled, onRejected) { function resolvePromise (promise2, x, resolve, reject) { if (promise2 === x) { // 不允许 promise2 === x; 避免自己等待自己 return reject(new TypeError('Chaining cycle detected for promise')); } // 防止重复调用 let called = false; try { if (x instanceof Promise) { let then = x.then; // 第一个参数指定调用对象 // 第二个参数为成功的回调,将结果作为 resolvePromise 的参数进行递归 // 第三个参数为失败的回调 then.call(x, y => { if (called) return; called = true; // resolve 的结果仍旧是 Promise 那就继续解析 resolvePromise(promise2, y, resolve, reject); }, err => { if (called) return; called = true; reject(err); }); } else { resolve(x); } } catch (e) { reject(e); } } // ...}优化 then 函数
then 函数的 onFulfilled 和 onRejected 参数允许不传.
Promise/A+ 规范要求 onFulfilled 和 onRejected 不能被同步调用,可以使用 setTimeout 改为异步调用。
then(onFulfilled, onRejected) { onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => { return v }; onRejected = typeof onRejected === 'function' ? onRejected : e => { throw e; }; function resolvePromise (promise2, x, resolve, reject) {...} let promise2 = new Promise((resolve, reject) => { function fulfilled () { setTimeout(() => { let x = onFulfilled(this.value); resolvePromise(promise2, x, resolve, reject); }, 0); }; function rejected () { setTimeout(() => { let x = onRejected(this.reason); resolvePromise(promise2, x, resolve, reject); }, 0); } if (this.status === RESOLVED) { fulfilled.call(this); } if (this.status === REJECTED) { rejected.call(this); } if (this.status === PENDING) { this.resolveCbs.push(fulfilled.bind(this)); this.rejectCbs.push(rejected.bind(this)); } }); return promise2;}catch 等方法实现
class Promise { // ... catch(fn) { this.then(null, fn); } static resolve (val) { return new Promise((resolve) => { resolve(val); }); } static reject (val) { return new Promise((resolve, reject) => { reject(val); }); } static race(promises) { return new Promise((resolve, reject) => { promises.map(promise => { promise.then(resolve, reject); }); }); } static all(promises) { let arr = []; let i = 0; return new Promise((resolve, reject) => { promises.map((promise, index) => { promise.then(data => { arr[index] = data; if (++i === promises.length) { resolve(arr); } }, reject); }) }) }}参考文章
BAT前台经典面试问题:史上最最最详细的手写Promise教程
假如你喜欢我的文章,希望可以关注一下我的公众号【前台develop】
前台develop
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 【面试题解析】手动实现Promise