Promise是ES6中的一种异步实现方式,现在我们来自己实现Promise。(有关Promise使用:ES6学习笔记 Promise)
下面会详细写出实现过程及遇到的问题,如果要看代码实现的话直接滑至博客底部
Promise构造
首先,我们创建一个promise.js来实现promise,使用class构造类并使用module.exports导出Promise
1 2 3 4 5 6
| class Promise { constructor(fn) { } }
module.exports = Promise
|
Promise要求传入一个函数作为参数,如果传入的不为函数,则会出现报错,我们来看看报错的内容
运行下面的代码
1 2 3
| new Promise(1).then(val => { console.log(val) })
|
得到如下的报错
所以我们在构造函数中加入对传入参数的类型判断
1 2 3 4 5 6 7 8
| class Promise { constructor(fn) { if (typeof fn !== 'function') { throw TypeError(`Promise resolver ${fn} is not a function`) } } }
|
Promise状态变换
接下来,我们知道,Promise有三个状态,进行中,已完成和已失败,我们需要一个值来记录这个状态,为了防止在后面写错,我们使用变量来记录三个状态对应的字符串,避免魔术字符串,并在使用resolve和reject方法时改变状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class Promise { constructor(fn) { if (typeof fn !== 'function') { throw TypeError(`Promise resolver ${fn} is not a function`) } this.state = Promise.PENDING; this.value = null; this.reason = null; try { fn(this.resolve, this.reject); } catch (e) { this.reject(e) } } resolve(val) { this.val = val; this.state = Promise.RESOLVE; } reject(reason) { this.reason = reason; this.state = Promise.REJECTED; } }
Promise.PENDING = "pending"; Promise.RESOLVE = "resolve"; Promise.REJECTED = "rejected";
|
状态改变后,我们就要来着手实现状态改变后的操作,在Promise中,我们使用then来实现状态改变后的操作,then方法接收两个参数,第一个是当状态变为resolve时调用,第二个是当状态变为rejected时调用。同样地,这里也需要进行参数的判断,当我们传入的参数不为函数时,会将这个值传下去,如下面的代码:
1 2 3 4 5 6 7 8 9 10 11
| new Promise((resolve, reject) => { resolve(1); }).then( 1 ).then((val) => { console.log(val) })
|
所以我们在写then方法时也许要对这部分内容做参数判断,在参数不为函数的情况下,模拟其默认实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| class Promise { constructor(fn) { } resolve(val) { this.val = val; this.state = Promise.RESOLVE; } reject(reason) { this.reason = reason; this.state = Promise.REJECTED; } then(onResolve, onRejected) { if (typeof onResolve !== 'function') { onResolve = (val) => { return val; } } if (typeof onRejected !== 'function') { onRejected = (reason) => { throw reason; } } if (this.state === Promise.RESOLVE) { onResolve(this.value); } else if (this.state === Promise.REJECTED) { onRejected(this.reason); } } }
|
运行测试代码
1 2 3 4 5 6 7
| const Promise = require('./promise.js');
new Promise((resolve) => { resolve(1); }).then((val) => { console.log(val) })
|
结果发现报了一堆错误。。。如图
可以看出这是由于value被undefined引用,而这里引用value的也就只有this了,所以是this没有指向Promise,经过排除,是resolve和reject方法没有绑定在this上,所以在构造方法上对其进行绑定,为了使功能更加清晰,将初始化值和绑定放在函数中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class Promise { constructor(fn) { if (typeof fn !== 'function') { throw TypeError(`Promise resolver ${fn} is not a function`) } this.initVal(); this.initBind(); try { fn(this.resolve, this.reject); } catch (e) { this.reject(e) }
} initVal() { this.state = Promise.PENDING; this.value = null; this.reason = null; } initBind() { this.resolve = this.resolve.bind(this); this.reject = this.reject.bind(this); } }
|
再测试上面的代码,正常输出了1,这部分的功能实现了。
我们知道,promise是异步的,看下面这段使用了ES6中的Promise的代码,看看输出顺序
1 2 3 4 5 6 7 8 9
| console.log(1); new Promise((resolve, reject) => { console.log(2); resolve('val'); }).then((val) => { console.log(4) console.log('val', val) }) console.log(3);
|
异步实现
我们知道Promise是异步的,所以这里的输出顺序是1 2 3 4 val,而刚才实现的Promise没有做到异步,同样测试上面这段代码,使用自定义的Promise,得到的结果是1 2 4 val 3。为了实现这个异步,可以使用setTimeout来做到
(这种做法存在一定的问题,setTimeout为微任务,而promise实际上为宏任务,使用setTimeout会使得与规范中Promise的执行顺序存在一定问题,详见详解JavaScript执行机制–异步执行顺序)
将then方法中的操作内容使用setTimeout包裹起来,这里setTimeout可以不设置时间,反正setTimeout是将其中的内容放在执行栈尾部,所以会在同步任务执行完后再执行里面的内容,同时,因为then返回一个Promise,所以我们使用一个Promise将其包裹起来。
为了实现链式调用,我们需要判断什么时候将then方法返回的promise改变状态,将resolve/reject执行后的值作为resolve的参数,并使用try catch捕获错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| then(onResolve, onRejected) { if (typeof onResolve !== 'function') { onResolve = (value) => { return value; } } if (typeof onRejected !== 'function') { onRejected = (reason) => { throw reason; } } let promise = new Promise((resolve, reject) => { setTimeout(() => { if (this.state === Promise.RESOLVE) { setTimeout(() => { try { const val = onResolve(this.value); resolve(val); } catch (e) { reject(e); } }) } else if (this.state === Promise.REJECTED) { setTimeout(() => { try { const val = onRejected(this.reason); resolve(val); } catch (e) { reject(e); } }) } }) }) return promise; }
|
按上面的方法,实现了异步,但是如果我们在new Promise
传入的参数函数中使用异步的话,又会怎样呢
1 2 3 4 5 6 7 8 9 10 11 12 13
| const Promise = require('./promise.js');
console.log(1); new Promise((resolve, reject) => { setTimeout(() => { console.log(2); resolve('val'); }) }).then((val) => { console.log(4) console.log('val', val) }) console.log(3);
|
我们会发现,最后只输出了1 3 2,这是因为由于在传入new Promise
的参数函数里的resolve执行前,已经执行了then,所以state实际上还是pending,我们可以在then方法返回的promise中加入this.state的输出,会发现输出的结果是pending。
要解决这个问题,我们可以在状态为pending时,将值储存起来,等待状态改变时再执行对应的操作,这里采用数组来存储,并在状态为pending时将value/reason push进对应的数组中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
| class Promise { constructor(fn) { if (typeof fn !== 'function') { throw TypeError(`Promise resolver ${fn} is not a function`) } this.initVal(); this.initBind(); try { fn(this.resolve, this.reject); } catch (e) { this.reject(e) }
} initVal() { this.state = Promise.PENDING; this.value = null; this.reason = null; this.resolveArr = []; this.rejectArr = []; } initBind() { this.resolve = this.resolve.bind(this); this.reject = this.reject.bind(this); } resolve(value) { if (this.state === Promise.PENDING) { this.value = value; this.state = Promise.RESOLVE; this.resolveArr.forEach(fn => fn(this.value)); } } reject(reason) { if (this.state === Promise.PENDING) { this.reason = reason; this.state = Promise.REJECTED; this.rejectArr.forEach(fn => fn(this.reason)); } } then(onResolve, onRejected) { if (typeof onResolve !== 'function') { onResolve = (value) => { return value; } } if (typeof onRejected !== 'function') { onRejected = (reason) => { throw reason; } } let promise = new Promise((resolve, reject) => { if (this.state === Promise.RESOLVE) { setTimeout(() => { try { const val = onResolve(this.value); resolve(val); } catch (e) { reject(e); } }) } else if (this.state === Promise.REJECTED) { setTimeout(() => { try { const val = onRejected(this.reason); resolve(val); } catch (e) { reject(e); } }) } else if (this.state === Promise.PENDING) { this.resolveArr.push((value) => { setTimeout(() => { try { onResolve(value) resolve(val); } catch (e) { reject(e); } }); }) this.rejectArr.push((value) => { setTimeout(() => { try { const val = onRejected(this.reason); resolve(val); } catch (e) { reject(e); } }); }) } }) return promise; } }
|
对返回内容的处理
至此,我们已经基本完成了Promise的内容,回到创建Promise实例的地方,我们每次resolve返回的值都为一个字符串或者一个数字,那么如果我们返回一个Promise对象呢
1 2 3 4 5 6 7 8 9 10 11
| const Promise = require('./promise.js');
new Promise((resolve, reject) => { resolve(1) }).then((val) => { return new Promise((resolve, reject) => { resolve(val); }) }).then((val) => { console.log('val', val) })
|
正常情况下这里应该输出1,但是在我们的测试代码中,却输出了整个Promise对象,这正是上面缺漏的地方,我们没对复杂的参数进行对应的处理。
在Promise中,Promise构造器第一个参数回调会展开thenable或真正的Promise,而第二个参数reject并不像第一个参数resolve一样,reject只会将传入的Promise/thenable值原封不动地设置为拒绝理由,不会展开Promise/thenable。
下面对代码进行修改,首先要将返回的promise对象中调用的resolve在执行前判断各种条件并做处理,这里使用一个新的方法来对这部分条件进行判断处理,该方法需要的参数有:当前then方法返回的promise,onResolve/onReject返回的值以及resolve和reject
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| Promise.resolvePromise = function(promise, val, resolve, reject) { if (val instanceof Promise) { val.then( value => { Promise.resolvePromise(promise, value, resolve, reject); }, reason => { reject(reason); } ) } else if (Object.prototype.toString.call(val) === "[object Object]" || Object.prototype.toString.call(val) === "[object Function]") { try { const then = val.then; if (typeof then === "function") { then.call( val, value => { Promise.resolvePromise(promise, value, resolve, reject); }, reason => { reject(reason); } ) } else { resolve(val) } } catch (e) { reject(e) } } else { resolve(val); } }
|
这个方法到这里基本可以解决问题了,但是如果在调用方法时,onResolve和onRejected同时被调用,按Promise的规定,我们会先执行优先变化的那个,而慢变化的那个,由于Promise状态的不可逆性,只能由pending变为resolve/rejected,所以慢变化的那个就会被忽略,所以我们再对上面的代码进行修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| Promise.resolvePromise = function(promise, val, resolve, reject) { let called = false; if (val instanceof Promise) { val.then( value => { Promise.resolvePromise(promise, value, resolve, reject); }, reason => { reject(reason); } ) } else if (Object.prototype.toString.call(val) === "[object Object]" || Object.prototype.toString.call(val) === "[object Function]") { try { const then = val.then; if (typeof then === "function") { then.call( val, value => { if (called) { return; } called = true; Promise.resolvePromise(promise, value, resolve, reject); }, reason => { if (called) { return; } called = true; reject(reason); } ) } else { if (called) { return; } called = true; resolve(val) } } catch (e) { if (called) { return; } called = true; reject(e) } } else { resolve(val); } }
|
Promise的重复调用
问题解决,来到下一个问题,如果Promise重复调用,会发生什么呢,看下面的代码
1 2 3 4 5 6
| let p1 = new Promise((resolve, reject) => { resolve(1); }) let p2 = p1.then(() => { return p2; })
|
这段代码的执行最终会报下图的错误
实际上就是then方法返回的内容与本身的promise相同,这样不断地循环调用,最终报错,我们在Promise.resolvePromise方法的开头进行判断并将上面的错误复制进去。
1 2 3 4 5 6 7
| Promise.resolvePromise = function(promise, val, resolve, reject) { if (promise === val) { reject(new TypeError('Chaining cycle detected for promise')) } }
|
最终代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
| class Promise { constructor(fn) { if (typeof fn !== 'function') { throw TypeError(`Promise resolver ${fn} is not a function`) } this.initVal(); this.initBind(); try { fn(this.resolve, this.reject); } catch (e) { this.reject(e) } }
initVal() { this.state = Promise.PENDING; this.value = null; this.reason = null; this.resolveArr = []; this.rejectArr = []; }
initBind() { this.resolve = this.resolve.bind(this); this.reject = this.reject.bind(this); }
resolve(value) { if (this.state === Promise.PENDING) { this.value = value; this.state = Promise.RESOLVE; this.resolveArr.forEach(fn => fn(this.value)); } }
reject(reason) { if (this.state === Promise.PENDING) { this.reason = reason; this.state = Promise.REJECTED; this.rejectArr.forEach(fn => fn(this.reason)); } }
then(onResolve, onRejected) { if (typeof onResolve !== 'function') { onResolve = (value) => { return value; } } if (typeof onRejected !== 'function') { onRejected = (reason) => { throw reason; } } let promise = new Promise((resolve, reject) => { if (this.state === Promise.RESOLVE) { setTimeout(() => { try { const val = onResolve(this.value); Promise.resolvePromise(promise, val, resolve, reject); } catch (e) { reject(e); } }) } else if (this.state === Promise.REJECTED) { setTimeout(() => { try { const val = onRejected(this.reason); Promise.resolvePromise(promise, val, resolve, reject); } catch (e) { reject(e); } }) } else if (this.state === Promise.PENDING) { this.resolveArr.push((value) => { setTimeout(() => { try { onResolve(value) Promise.resolvePromise(promise, val, resolve, reject); } catch (e) { reject(e); } }); }) this.rejectArr.push((value) => { setTimeout(() => { try { const val = onRejected(this.reason); Promise.resolvePromise(promise, val, resolve, reject); } catch (e) { reject(e); } }); }) } }) return promise; } }
Promise.PENDING = "pending"; Promise.RESOLVE = "resolve"; Promise.REJECTED = "rejected"; Promise.resolvePromise = function(promise, val, resolve, reject) { if (promise === val) { reject(new TypeError('Chaining cycle detected for promise')) } let called = false; if (val instanceof Promise) { val.then( value => { Promise.resolvePromise(promise, value, resolve, reject); }, reason => { reject(reason); } ) } else if (Object.prototype.toString.call(val) === "[object Object]" || Object.prototype.toString.call(val) === "[object Function]") { try { const then = val.then; if (typeof then === "function") { then.call( val, value => { if (called) { return; } called = true; Promise.resolvePromise(promise, value, resolve, reject); }, reason => { if (called) { return; } called = true; reject(reason); } ) } else { if (called) { return; } called = true; resolve(val) } } catch (e) { if (called) { return; } called = true; reject(e) } } else { resolve(val); } }
module.exports = Promise
|
基本实现了Promise的大部分内容,出现什么错误请路过大佬指出。
参考文档:Promises/A+规范