Promise 对象用于表示一个异步操作的最终完成 (或失败), 及其结果值.它是用于解决回调地狱的尴尬与丑陋的神器。
这里附上MDN地址:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise。
可以看下具体的用法。但是今天我们试着手写一个简单(简陋)的promise,来进一步认识她是如何处理异步操作的。
就像汽车行业的逆向研发一样,我们也是根据 promise使用的API,来倒推出promise源码,这样会比较好理解。
1、promise的用法
想要写源码,我们其实根据其API倒退我们准备写的源码。毕竟我们现在是在逆向开发。所以看看我们用常用的promise用法。先看一下最常用的以下两种场景:
//场景一:直接调用 -> 这就意味着在写源码的时候 Promise方法上本身绑定的就有 resolve 或者 reject的属性
Promise.resolve(1000); // -> 成功的值
Promise.reject('some error'); // -> 失败的值
//场景二:实例化 -> 这就意味着原型方法上要有个 then的方法
new Promise((resolve, reject) => {
// 这里来个异步的代码
settimeout(() => {
resolve(1000);
},1000)
}).then(res => {
console.log('接受的结果', res)
})
2、Promise 构造函数的‘逆向研发’
我们先来打一个基本的架子 - 构造函数。
// 毫无疑问, Promise是一个构造函数。
// 直接上构造函数的基本配置:大写函数,原型方法
function Promise () {
}
// 上边我们说了,实例方法 有个 then 的方法用来接收结果的,那么 这个方法定义在 prototype上更合适
Promise.prototype.then = function () {
// 这里接收结果
}
架子好了之后,我们要考虑这构造函数中都有些设么东西。
A、状态 status
我们知道promise内部的三大状态: pending(等待),fulfilled(成功), rejected(失败)。promise对象的状态,从_Pending_转换为_Fulfilled_或_Rejected_之后, 这个promise对象的状态就不会再发生任何变化。如下图:
那么构造函数理应有个 状态(status)的属性;
function Promise () {
// 默认的初始状态
this.status = 'pending';
}
B、值(错误) value或error
有了状态 之后,要有对应的值。
function Promise () {
// 状态
this.status = 'pending';
// 成功的值 对应的是resolve ,默认为 null
this.value = null;
// 失败的原因 对应的是reject,默认为null
this.reason = null;
}
C、构造函数传参 executor
executor是带有 resolve
和 reject
两个参数的函数 。
说完了上边的值和错误,肯定是有两个函数分别用来处理对应的值: resolve(处理成功的值 - value),reject(处理错误的信息 - error)。注意,这两个参数都是函数。
// executor -> 是一个函数 函数包含了resolve, reject两个函数;
// 我们用promise的时候也是这样的 new Promise((resolve, reject) => {resolve(1000)})
function Promise (executor ) {
if (typeof executor != 'function') {
throw new Error(`${executor} is not a function`)
}
this.status = 'PENDING';// 状态
this.value = null; // 成功 resolve 的值
this.reason = null;// 失败 reject 的值
this.resolve = (value) => {
// 确保状态是 pending才能 更新状态
if (this.status == 'pending') {
this.status = 'fulfilled';
this.value = value;
}
}
this.reject = (reason) => {
// 确保状态是 pending才能 更新状态
if (this.status == 'pending') {
this.status = 'rejected';
this.reason = reason;
}
}
// 执行这个传递进来的函数
executor(this.resolve, this.reject);
}
D、then 方法
不论是成功或是失败都会调用then方法,用来接收传递过来的值。
Promise.prototype.then = function (onFulFilled, onRejected) {
// -> 成功时传值的处理
if (this.status == 'fulfilled') {
if (typeof onFulFilled == 'function') {
onFulFilled(this.value)
}
}
// -> 失败时传值的处理
if (this.status == 'rejected') {
if (typeof onRejected == 'function') {
onRejected(this.reason)
}
}
}
好了,现在主体的方法基本处理完了。现在我们来试验一下,我们写的这个promise
// 场景一: -> 用来处理 同步的代码
new Promise((resolve, reject) => {
console.log('promise 执行了') // -> 输出成功,同步代码通过了
})
// 但是我们一开始就说,promise的用处在于处理异步操作。
// 场景二: -> 用来处理 异步代码
new Promise((resolve, reject) => {
settimeout(() => {
// 异步代码操作 以成功传值为例
resolve(1000)
},1000)
}).then(res => console.log(res))
// -> 结果是 没有任何输出结果。
处理异步的时候,我们目前的代码并不能执行。为什么呢?我们要注意,异步代码并不会立即执行。而是在主体代码执行之后,才会来处理异步代码。所以,在一开始执行then方法的时候,promise的状态保持在 pending。那么既然pending,因此then方法即没有调用onFulfilled也没有调用onRejected。所以这个值根本就不会被捕获到。
那么如何处理异步代码?
F、处理异步代码
上边说到,then执行的时候 promise的状态始终在 pending. 所以,并不能处理回调。那么我们在他 pending的时候用一个数组,把它存起来,在 resolve 的时候执行就可以了。
function promise (executor) {
this.status = 'pending';
this.value = null;
this.reason = null;
// 在这里加上两个暂存数组 分别存储 resolve的异步函数和reject的异步函数
this.onFulFilledAry = [];
this.onRejectedAry = [];
this.resolve = value = > {
if (this.status == 'pending') {
this.value = value;
this.status = 'fulfilled'
// 在这里执行所有resolve暂存的回调
this.onFulFilledAry.forEach(fn => fn(value))
}
}
this.reject = reason => {
if (this.status == 'pending') {
this.status = 'rejected';
// 在这里执行所有的reject的暂存回调
this.onRejectedAry.forEach(fn => fn(reason))
this.reason = reason;
}
}
executor(this.resolve, this.reject);
}
Promise.prototype.then = function (onFulFilled, onRejected) {
// 这里主要处理异步代码的执行
if (this.status == 'pending') {
if (typeof onFulFilled == 'function) {
// 成功的回调函数存进暂存的数组中
this.onFulFilledAry.push(onFulFilled )
}
if (typeof onRejected== 'function) {
// 失败的回调函数存进暂存的数组中
this.onRejectedAry.push(onRejected)
}
}
// 下边的resolve和reject的状态的代码不写了
......
}
我们再次测试一下,异步的代码。
let p = new Promise((resolve, reject) => {
settimeout(() => {
resolve(1000)
}, 1000)
})
p.then(res => console.log(res)) // -> 成功输出 1000
至此,Promise已经支持了异步操作,promise最基本的功能已经基本实现了。
至于还有其他的API,比如 promise.all promise.race等等。后续有时间继续探究分享。