我正在参与掘金创作者训练营第5期,点击了解活动详情
简述
-
面试官: 讲一下对 promise 的理解吧
- 一脸兴奋:我会手写!
-
面试官: 那写吧
- 打开 vsCode emmm 那个那个 这有点忘了
-
面试官:.....那就这样吧 回去等通知
-
yysy 手写 promise 是挺难的 如果流程不太清楚的话 很有可能会卡住 不过手写都不容易
-
下面还是介绍一下整个 从零到一的思路吧 理解清除还是能去碰一碰的
- 会手写就有底气嘛 哈哈哈
分析
-
要手写的先知道 Promise 有哪些功能 然后才能根据相应的功能完成代码
-
可以结合自己平时写得以及测试 来看看 Promise 有哪些功能嘛
-
1.三个状态 状态不可能
-
2.有两个回调 resolve reject
-
3.异步执行
-
4..then()
- 返回一个 Promise 对象
- 可以传递两个回调 也可以不穿参数
- 可以链式调用 值传递 值穿透 直到状态改变才返回结果
-
5..catch()
- 返回失败结果
-
6..all()
- 全部成功才成功
-
7..race()
- 返回最先改变状态得结果
-
-
首先还是得先写构造函数 之后再为函数添加方法
-
下面会按照顺序进行编写
测试代码
- 可以控制
<script src="./promise.js"></script>得引入和原生得 Promise 进行对比
-
<script src="./promise.js"></script> <script> // test console.log('同步任务 111') let p = new Promise((resolve, reject) => { resolve('ok') // reject('error') // setTimeout(() => { // resolve('ok') // // reject('error') // }, 500); }) // console.log(p, '同步任务 p') let res = p.then(value => { console.log('异步任务 111') return value }, reason => { return reason }) res.then(value => { console.log(value, '异步任务 222') return value }, reason => { return reason }).then().catch(err => { console.log(err) }) console.log(res, '同步任务 res') console.log('同步任务 222') let p1 = new Promise(resolve => resolve('p1')) let p2 = new Promise(resolve => resolve('p2')) /* + 这里要注意一点 + 要写成 Promise.prototype.all() + 因为我定义在原型链上面 + 如果想直接 Promise.all() 那就直接定义在 Promise 构造函数上面就行了 + 不对啊? 那为什么上面可以使用 .then + 因为那是实例对象 p 上使用 他会通过 隐式原型 _proto_ 查找到 .then + 这里不懂 我看看抽时间写一篇 原型 原型链的博客吧 + 不过这里 all 方法不需要定义在原型链上 本来就是 Promise 独有的 并且实例对象也用不到 all 方法 */ // let resAll = Promise.all([p1, p2]) let resAll = Promise.prototype.all([p1, p2]) console.log(resAll, 'resAll') </script>
Promise 构造函数
-
创建一个 Promise 构造函数
-
参数
-
传入的是带两参数的回到函数 这里用 executor 接收
- 本质其实是个执行函数 其中两个参数函数就是 下方定义的 resolve 和 reject
-
并且这两个函数要通过 executor 调用一次
// 这里没使用 resolve 是强调一下这个作为参数传到 Promise 内部 然后通过 executor 执行一下这个函数 new Promise((so, re) => { so('ok) })
-
-
属性
-
PromiseState 状态
-
PromiseResult 结果
-
callBacks 存储回调函数
- 这里存储的是相应的回调函数
- 当执行到 resolve 或者 reject 时 会将对应的 resolve 或者 reject 全部执行
- 里面的细节会在下方结合代码说明
-
-
构造函数会自动执行两个函数
- resolve 成功的回调
- reject 失败的回调
- 1.因为状态不可逆 所以状态改变后直接返回
- 2.修改相应的状态 和 值
- 3.因为执行这个函数就意味着改变了状态 可以执行之前存储的回调函数
-
-
function Promise(executor) { // 添加属性 this.PromiseState = 'pending' this.PromiseResult = null this.callbacks = [] // 存对象 对象中两个函数 onReject onResolve // 保存实例对象 this 的值 let self = this function resolve(data) { // 为了确保状态确定后不执行其他回调 if (self.PromiseState !== 'pending') return // 修改对象的状态 self.PromiseState = 'fulfilled' self.PromiseResult = data // 所有回调函数的结果都是由最后执行的这个 resolve 或者 reject 的值来决定 // 模拟异步 setTimeout(() => { self.callbacks.forEach(item => { item.onResolve(data) }) }, 0) } function reject(data) { if (self.PromiseState !== 'pending') return // 修改对象的状态 self.PromiseState = 'reject' self.PromiseResult = data setTimeout(() => { self.callbacks.forEach(item => { item.onReject(data) }) }, 0) } // 同步调用 执行器函数 try { // console.log(resolve, reject) executor(resolve, reject) } catch (e) { reject(e) } }
.then 方法
-
then 函数
-
参数 两个回调函数
- onResolve
- onReject
-
返回一个 Promise 对象
- 因为返回值为 Promise 所以支持链式调用
- p.then().then().then()
-
根据不同的状态执行相应的函数
-
fulfilled
-
reject
- 如果是结果了 那就直接返回 如果是 Promise 就继续 .then 调用
// 这种情况返回的就是 Promise 需要继续判断 p.then(res => { return new Promise() }) -
pending
-
如果传递的是一个同步任务 是立刻改变状态的
-
但是异步任务 那么需要改变状态以后才执行回调 所以这些回调函数 要在 resolve 或 reject 中执行
-
这些回调函数状态还是 pending 不确定的 要先保存到 callBacks 中
-
因为只要状态没改变就 push 所以可以支持多个回调
-
let p = new Promise() p.then p.then p.then
- 链式回调是因为返回值为 Promise 的原因
-
-
-
-
-
Promise.prototype.then = function (onResolve, onReject) { let self = this /** * 这两个判断的作用是什么呢?举例吧 * p.then().then(res => {}) * 看出来了吧 要是前一个 then 不传参数就是 undefined 那我后面要执行 callBack 函数怎么执行? * 所以可以为空 但是给他添加一个默认函数 */ // if (typeof onReject !== 'function') { onReject = reason => { throw reason } } if (typeof onResolve !== 'function') { onResolve = value => value } /** * 注意 链式调用的返回结果是一个新的 Promise * 而这个新的 promise 的值取决于上一个 promise 的返回值 * 比如 上一个执行结果为 reject 那么 */ return new Promise((resolve, reject) => { // 根据状态调用对应得 回调 function callBack(type) { // type 执行的是 .then 中的回调函数 返回 res // 而这个 res 是根据 上一个 Promise 的 PromiseState 和 PromiseResult 确定的 // self 就说明了这一点 // 这个新的 new Promise() 执行的回调会根据上一个 状态来执行 if else 中的回调函数 // 唯一要看的是 pending 中添加的回调 // 这里要注意的的是 上一个结果可能是 reject 但是对于这一个 Promise 来说 他已经决定状态了 // 既然决定好状态了 那么就直接调用 resolve 就行了 try { // 用 try catch 包裹 为了防止抛出错误直接终止运行了 let res = type(self.PromiseResult) // console.log(res, 'in') // 获取当前调用的结果 if (res instanceof Promise) { res.then(val => { resolve(val) }, rea => { reject(rea) }) } else { resolve(res) } } catch(e) { reject(e + '-err') } } if (this.PromiseState === 'fulfilled') { // 模拟异步 setTimeout(() => { callBack(onResolve) }, 0) } if (this.PromiseState === 'reject') { // 模拟异步 setTimeout(() => { callBack(onReject) }, 0) } if (this.PromiseState === 'pending') { // 保存回调函数 this.callbacks.push({ onResolve: function () { callBack(onResolve) }, onReject: function () { callBack(onReject) } }) } }) }
.catch 方法
-
catch 方法
-
接收最后失败的回调
-
.then() 方法是可以接收最后失败的回调的
-
这说明了什么呢 说明 .catch 方法是和 .then() 方法本质一样的
-
那么区别是什么?
- 就是他只接收失败的方法呗
-
-
Promise.prototype.catch = function (onReject) { return this.then(undefined, onReject) }
.all 方法
-
all 方法
- 只有传入的函数全部成功才返回成功
-
参数
- 传入的是一个数组
-
返回一个 Promise 对象
-
要将 数组中的所有 promise 全部执行
-
这里要使用一个 count 计数
-
一个数组存储执行结果
- 并且添加的结果要按下标添加 可能是异步函数 push 顺序会乱
-
-
Promise.prototype.all = function (promises) { return new Promise((resolve, reject) => { let count = 0 let resArr = [] for (let i = 0; i < promises.length; i++) { promises[i].then(res => { count++ // 按顺序将对应结果放入结果数组 resArr[i] = res // 只有全部成功的才执行成功回调 if (count === promises.length) resolve(resArr) }, rea => { reject(rea) }) } }) }
race 方法
- 这个就比较简单了
- 直接遍历 谁先改变谁就直接执行
-
Promise.race = function (promises) { return new Promise((resolve, reject) => { for (let i = 0; i < promises.length; i++) { promises[i].then(res => { resolve(res) }, rea => { reject(rea) }) } }) }
总结
- 整个流程还是挺复杂还 最好还是自己敲一遍 我之前没搞懂得地方有在相应代码位置注释 应该能够看懂吧
- 还是得自己多花时间思考 自己理解了才是自己的 ❤❤❤