前言
最近复盘面试知识点,问到异步编程的解决方案这一块,发现自己的认识并不清晰,很容易被问倒。本文主要记录了自己实现一个promise的全过程,建议大家配合Promise A+规范一起阅读。
Promise? what & why
Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值。ES6规范提出Promise对象可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者(语义化更好的aysnc/await
只是promise和generator的语法糖。async-await
是建立在 promise机制之上的)
Promise的基本结构
Promise构造函数接受一个函数作为参数,我们称该函数为excuter
,excuter
又包含resolve和reject两个参数,它们是两个函数。promise初始化的时候就会执行这个函数,
new Promise((resolve, reject) => {
setTimeout(() => {
resolve("resolve");
}, 1000);
});
Promise基本状态与值
一个 Promise 必然处于以下几种状态之一:
- 待定(pending): 初始状态,在此状态下可以落定 (settled) 为 fulfilled 或 rejected状态。
- 已兑现(fulfilled): 意味着操作成功完成,返回一个私有的值value
- 已拒绝(rejected): 意味着操作失败,返回一个私有的值reason
const PENDING = "Pending"; //等待
const FULLFILLED = "Fullfilled"; //执行
const REJECTED = "Rejected"; //拒绝
class myPromise {
constructor(excuter) {
if (typeof excuter !== "function") {
throw new Error("myPromise must accept a function");
}
this.state = PENDING;
this.reason = undefined;
this.value = undefined;
try {
excuter(this._resolve.bind(this), this._reject.bind(this));
} catch (error) {
this.reject(error);
}
}
// Promise类还具有静态方法 resolve 避免重名
_resolve(val) {
if (this.state !== PENDING) return;
this.state = FULLFILLED;
this.value = val;
}
_reject(err) {
if (this.state !== PENDING) return;
this.state = REJECTED;
this.reason = err;
}
}
最重要的then方法
提供一个 then 方法来访问当前或最终的 value 或 reason,then方法需要满足:
- 接收两个函数作为参数,参数可选
- 当参数不为函数时会被忽略(2.2.1)
- 两个函数都是异步执行,会放入事件队列等待下一轮 tick
- 当调用 onFulfilled 函数时,会将当前 Promise 的 value 值作为参数传入。
- 当调用 onRejected 函数时,会将当前 Promise 的 reason 失败原因作为参数传入。
- onFulfilled 与 onRejected调用次数不可超过一次,状态改变前其不可被调用
- then 函数的返回值为 Promise,这是then可以链式调用的原因
- then 可以被同一个 Promise 多次调用
参照上面这些规则,我们可以完善我们的myPromise,首先需要支持多次调用,这点可以用两个数组onFullfilledQueues,onRejectedQueues
来维护,通过在then方法注册的时候将回调添加到队列中,等待状态改变执行。
constructor(excuter) {
if (typeof excuter !== "function") {
throw new Error("myPromise must accept a function");
}
this.state = PENDING;
this.reason = undefined;
this.value = undefined;
// 添加成功回调函数队列
this.onFullfilledQueues = [];
// 添加失败回调函数队列
this.onRejectedQueues = [];
try {
excuter(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error);
}
}
实现then方法,当一个 Promise 的状态被 fulfilled 之后,会执行其回调函数,而回调函数返回的结果会被当作 value,返回给下一个 Promise(也就是then 中产生的 Promise),同时下一个 Promise的状态也会被改变(执行 resolve 或 reject),然后再去执行其回调,以此类推下去... 下面参考promise解决程序(2.3)实现一个解决过程:
// Promise 解决过程2.3
resolvePromise(promise, x, resolve, reject) {
// 2.3.1
if (promise === x) {
return reject(new TypeError("error found in: promise A+ 2.3.1"));
}
//2.3.2
if (x instanceof myPromise) {
if (x.state === FULLFILLED) {
resolve(x.value);
} else if (x.state === REJECTED) {
reject(x.reason);
} else {
x.then((y) => {
this.resolvePromise(promise, y, resolve, reject);
}, reject);
}
} else if (//2.3.3
x !== null &&
(typeof x === "object" || typeof x === "function")
) {
var executed;
try {
var then = x.then;
if (typeof then === "function") {
then.call(
x,
(y) => {
if (executed) return;
executed = true;
this.resolvePromise(promise, y, resolve, reject);
},
(e) => {
if (executed) return;
executed = true;
reject(e);
}
);
} else {
resolve(x);
}
} catch (e) {
if (executed) return;
executed = true;
reject(e);
}
} else {
resolve(x);
}
}
根据上文中 then 方法的规则,我们知道返回的新的 Promise 对象的状态依赖于当前 then 方法回调函数执行的情况以及返回值,我们举几个场景说明:
- 如果 onFulfilled 或者 onRejected 返回一个值 x,
- 若 x 不为 Promise ,则使 x 直接作为新返回的 Promise 对象的值 -- 见第四点
- 若 x 为 Promise ,这时后一个回调函数,就会等待该 Promise 对象(即 x )的状态发生变化,才会被调用,并且新的 Promise 状态和 x 的状态相同。
//1.非promise
let promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, 1000)
})
promise2 = promise1.then(res => {
// 返回一个普通值
return '这里返回一个普通值'
})
promise2.then(res => {
console.log(res) //1秒后打印出:这里返回一个普通值
})
//2. promise
let promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, 1000)
})
promise2 = promise1.then(res => {
// 返回一个Promise对象
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('这里返回一个Promise')
}, 2000)
})
})
promise2.then(res => {
console.log(res) //3秒后打印出:这里返回一个Promise
})
- 如果 onFulfilled 或者onRejected 抛出一个异常 e ,则promise2必须变为失败(Rejected),并返回失败的值 e
let promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, 1000)
})
promise2 = promise1.then(res => {
// 返回一个Promise对象
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('这里返回一个Promise')
}, 2000)
})
})
promise2.then(res => {
console.log(res) //3秒后打印出:这里返回一个Promise
})
- 如果onFulfilled 不是函数且 promise1 状态为成功(Fulfilled), promise2 必须变为成功(Fulfilled)并返回 promise1 成功的值 -- 规范第二点,当参数不为函数时会被忽略
- 如果 onRejected 不是函数且 promise1 状态为失败(Rejected),promise2必须变为失败(Rejected) 并返回 promise1 失败的值
根据上述规则以及解决过程完善then方法
//实现Promise的then方法
then(onFullfilled, onRejected) {
onFullfilled =
typeof onFullfilled === "function"
? onFullfilled
: function (x) {
return x;
};
onRejected =
typeof onRejected === "function"
? onRejected
: function (e) {
throw e;
};
// 返回一个新的Promise对象
let promise = new myPromise((resolve, reject) => {
switch (this.state) {
case PENDING:
this.onFullfilledQueues.push(() => {
try {
var x = onFullfilled(this.value);
this.resolvePromise(promise, x, resolve, reject);
} catch (error) {
reject(error);
}
});
this.onRejectedQueues.push(() => {
try {
var x = onRejected(this.reason);
this.resolvePromise(promise, x, resolve, reject);
} catch (error) {
reject(error);
}
});
break;
case FULLFILLED:
try {
var x = onFullfilled(this.value);
this.resolvePromise(promise, x, resolve, reject);
} catch (error) {
reject(error);
}
break;
case REJECTED:
try {
var x = onRejected(this.reason);
this.resolvePromise(promise, x, resolve, reject);
} catch (error) {
reject(error);
}
break;
}
});
return promise;
}
分析一下代码的执行:
- 实例化promsie的过程实例上会执行传入的函数,将类内部的resolve和reject 绑到形参
- 遇到then方法会将参数存到这个实例的执行队列上(之前的回调函数),then 中返回了新的 Promise,但是then中注册的回调仍然是属于上一个 Promise 的,then 方法可被同一个 promise 调用多次
- 明确一点: promise的状态只由他内部的resolve reject中改变
- Promise里的关键是要保证,then方法传入的参数 onFulfilled 和 onRejected,必须在then方法被调用的那一轮事件循环之后的新执行栈中执行
- 当promise状态改变的时候 会执行队列里的函数 然后根据现在的状态决定下一个promise的状态(then返回那个),我们把then方法返回新promise的参数(resolve,reject)传给了解决过程resolvePromise,这样我们就可改变这个新promise的状态。这就是链式调用的原理。
catch方法
其实就是只处理错误状态的then方法
// 添加catch方法
catch (onRejected) {
return this.then(undefined, onRejected)
}
还有其他静态方法,先占个坑
测试工具 promises-tests
使用方法:
- git clone
- 在文件中导出出测试的接口
- 修改测试命令 在package.json中测试自己的promise.js文件(根据自己的文件而定)
"test": "promises-aplus-tests promise.js
跑完测试需要一点时间,此工具用例没有测试到resolve()中传入promsie的情况,我在童欧巴大佬的1.手写 Promise 全家桶讨论过这个issue,大佬的文章质量都很高,大家可以follow学习。 传送: 大佬github
完整代码
const PENDING = "Pending"; //等待
const FULLFILLED = "Fullfilled"; //执行
const REJECTED = "Rejected"; //拒绝
class myPromise {
constructor(excuter) {
if (typeof excuter !== "function") {
throw new Error("myPromise must accept a function");
}
this.state = PENDING;
this.reason = undefined;
this.value = undefined;
// 添加成功回调函数队列
this.onFullfilledQueues = [];
// 添加失败回调函数队列
this.onRejectedQueues = [];
try {
excuter(this._resolve.bind(this), this._reject.bind(this));
} catch (error) {
this._reject(error);
}
}
//需要异步调用数组中的函数,这里使用 setTimeout 来模拟异步。
_resolve(val) {
// console.log(val);
//2.3. Promise解决程序
if (val instanceof myPromise) {
return val.then(this._resolve.bind(this), this._reject.bind(this));
// return val.then(this._resolve, this._reject);
}
const run = () => {
if (this.state !== PENDING) return;
this.state = FULLFILLED;
this.value = val;
// 依次执行成功队列中的函数,并清空队列
let cb;
while ((cb = this.onFullfilledQueues.shift())) {
cb(this.value);
}
};
// 为了支持同步的Promise,这里采用异步调用
setTimeout(run);
}
_reject(err) {
const run = () => {
if (this.state !== PENDING) return;
this.state = REJECTED;
this.reason = err;
// 依次执行失败队列中的函数,并清空队列
let cb;
while ((cb = this.onRejectedQueues.shift())) {
cb(err);
}
};
setTimeout(run);
}
// Promise 解决过程2.3
resolvePromise(promise, x, resolve, reject) {
// console.log(promise, x);
if (promise === x) {
return reject(new TypeError("error found in: promise A+ 2.3.1"));
}
if (x instanceof myPromise) {
if (x.state === FULLFILLED) {
resolve(x.value);
} else if (x.state === REJECTED) {
reject(x.reason);
} else {
x.then((y) => {
this.resolvePromise(promise, y, resolve, reject);
}, reject);
}
} else if (
x !== null &&
(typeof x === "object" || typeof x === "function")
) {
var executed;
try {
var then = x.then;
if (typeof then === "function") {
then.call(
x,
(y) => {
if (executed) return;
executed = true;
this.resolvePromise(promise, y, resolve, reject);
},
(e) => {
if (executed) return;
executed = true;
reject(e);
}
);
} else {
resolve(x);
}
} catch (e) {
if (executed) return;
executed = true;
reject(e);
}
} else {
resolve(x);
}
}
//实现Promise的then方法
then(onFullfilled, onRejected) {
onFullfilled =
typeof onFullfilled === "function"
? onFullfilled
: function (x) {
return x;
};
onRejected =
typeof onRejected === "function"
? onRejected
: function (e) {
throw e;
};
// 返回一个新的Promise对象
let promise = new myPromise((resolve, reject) => {
switch (this.state) {
case PENDING:
this.onFullfilledQueues.push(() => {
try {
var x = onFullfilled(this.value);
this.resolvePromise(promise, x, resolve, reject);
} catch (error) {
reject(error);
}
});
this.onRejectedQueues.push(() => {
try {
var x = onRejected(this.reason);
this.resolvePromise(promise, x, resolve, reject);
} catch (error) {
reject(error);
}
});
break;
case FULLFILLED:
try {
var x = onFullfilled(this.value);
this.resolvePromise(promise, x, resolve, reject);
} catch (error) {
reject(error);
}
break;
case REJECTED:
try {
var x = onRejected(this.reason);
this.resolvePromise(promise, x, resolve, reject);
} catch (error) {
reject(error);
}
break;
}
});
return promise;
}
}
后记
代码中有疑问或者不对的地方欢迎各位批评指正,共同进步。求点赞三连QAQ
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!