前言
目标:封装一个promise,更好的理解promise底层逻辑需求:实现以下promise所有的功能和方法 如下图所示
一、构造函数编写
步骤
1、定义一个TestPromise类,
2、添加构造函数,
3、定义resolve/reject,
4、执行回调函数
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>手写Promise</title>
</head><body><h2>构造函数</h2><script>// 1. 定义类class TestPromise{// 2. 添加构造函数constructor(func){// 3、声明 resolve rejectconst resolve = (result)=>{// TODOconsole.log("resolve",result)}const reject = (result)=>{// TODOconsole.log("reject",result)}// 4. 执行回调函数func(resolve,reject)} }// ------------- 测试代码 -------------const p = new TestPromise((resolve, reject) => {console.log("调用了")resolve('success')// reject('error')})</script>
</body></html>
二、promise的状态和原因
分析
promise有pending->fulfilled pending->rejected,
所以我们要为我们的实例类添加状态以及导致状态变化的原因
state状态 result原因
而且当pending状态一旦发生变化,便不可逆
步骤
1、添加状态(pending / fulfilled / rejected)
2、添加原因
3、调整resolve/reject
4、状态不可逆
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>手写Promise</title>
</head><body><h2>构造函数</h2><script>// 通过变量保存状态,便于后续使用const PENDING = 'pending'const FULFILLED = 'fulfilled'const REJECTED = 'rejected'class TestPromise{// 1. 添加状态(pending / fulfilled / rejected)state = PENDING// 2. 添加原因result = undefinedconstructor(func){// 改状态: pending->fulfilled// 记录原因const resolve = (result)=>{// 加判断状态不可逆if(this.state===PENDING){this.state = FULFILLEDthis.result = result}}// 改状态: pending->rejected// 记录原因const reject = (result)=>{if(this.state===PENDING){this.state = REJECTEDthis.result = result}}func(resolve,reject)} }// ------------- 测试代码 -------------const p = new TestPromise((resolve, reject) => {resolve("fulfilled")// 只会执行上面的 状态变成fulfilled后下面不再执行reject("rejected")})</script>
</body></html>
三、then方法
分析
promise有成功和失败回调,异步多次调用
步骤一:成功和失败回调
1、添加实例方法
2、参数判断,判断传入的是不是回调函数
3、根据状态执行不同的回调函数(成功or失败)
注意如果传入的不是函数,成功和失败的回调默认实现是不同的,以下是文档,我们参考文档实现
class TestPromise{···constructor(func){···}// 1、添加实例方法then(onFulfilled, onRejected){// 2、参数判断,判断传入的是不是回调函数// 是函数返回函数,不是返回原值onFulfilled = typeof onFulfilled === 'function'?onFulfilled:x=>x// 是函数返回函数,不是抛出onRejected = typeof onRejected === 'function'?onRejected:x=>{ throw x}// 3、根据状态执行不同的回调函数(成功or失败)if(this.state === FULFILLED){onFulfilled(this.result)}else if(this.state === REJECTED){onRejected(this.result)}}}// ------------- 测试代码 -------------···p.then(res=>{console.log('成功回调',res)}, err=>{console.log('失败回调',err)})
步骤二:异步和多次调用
1、定义实例属性(pending状态下保存then保存的回调函数)
2、执行保存的成功和失败回调
class TestPromise{···// 1、对象数组保存成功和失败的回调函数{onFulfilled, onRejected} // # 定义属性私有,只有内部可以访问到#handlers = [] constructor(func){const resolve = (result)=>{if(this.state===PENDING){this.state = FULFILLEDthis.result = result// 3、执行成功的回调this.#handlers.forEach(({onFulfilled})=>{onFulfilled(this.result)})}}const reject = (result)=>{if(this.state===PENDING){this.state = REJECTEDthis.result = result// 3、执行失败的回调this.#handlers.forEach(({onRejected})=>{onRejected(this.result)})}}func(resolve,reject)}then(onFulfilled, onRejected){onFulfilled = typeof onFulfilled === 'function'?onFulfilled:x=>xonRejected = typeof onRejected === 'function'?onRejected:x=>{ throw x}if(this.state === FULFILLED){onFulfilled(this.result)}else if(this.state === REJECTED){onRejected(this.result)}else if(this.state === PENDING){// 2、保存回调函数this.#handlers.push({onFulfilled, onRejected})}}}// ------------- 测试代码 -------------const p = new TestPromise((resolve, reject) => {// 异步setTimeout(()=>{resolve("fulfilled")// reject("rejected")},2000)})p.then(res=>{console.log('then1',res)}, err=>{console.log('then1',err)})p.then(res=>{console.log('then2',res)}, err=>{console.log('then2',err)})
解析:当定义类存在setTimeout时,这时的state属性为pending,then执行了
(所以我们要在then方法中加保存当前回调函数,当倒计时结束,调取resolve时我们执行回调函数数组)
四、异步任务
分析
promise.then()里面执行的是异步任务
所以我们的promise中也要实现异步处理,实现{1、核心API2、函数封装
}
核心API,vue2中执行异步的API有Promise.then、MutationObserver、setImmediate、setTimeout
选用queueMicrotask、MutationObserver、setTimeout
queueMicrotask:直接执行一个异步任务(node11开始支持、支持新式浏览器、IE不支持)
MutationObserver:dom节点改变执行异步任务(IE11支持)
setTimeout都支持
// 使用
queueMicrotask((fun)=>{fun() // 回调函数直接异步执行
})
const obs = new MutationObserver(()=>{// ...
})
const divNode = document.createElement('div')
// 参数一:观察的dom节点 参数二:观察的选项 childList观察子节点
obs.observe(divNode, { childList: true }) // 检测子节点是否改变
divNode.innerText = 'tets' // 开始修改子节点
// 节点发生改变执行异步回调
基于核心API完成异步任务的函数封装
1、定义函数,接收一个回调函数
2、调用核心api(queueMicrotask,MutationObserver,setTimeout)
3、在我们的promise调用封装的函数
1、定义函数,接收一个回调函数
// 1、定义函数function runAsynctask(callback){// 2. 调用核心api(queueMicrotask,MutationObserver,setTimeout)if(typeof queueMicrotask === 'function'){queueMicrotask(callback)}else if(typeof MutationObserver === 'function'){const obs = new MutationObserver(callback)const divNode = document.createElement('div')obs.observe(divNode, { childList: true })// 不需要把节点添加到页面divNode.innerText = 'test'}else{setTimeout(callback,0)}}
2、在我们的promise调用封装的函数
class TestPromise{···then(onFulfilled, onRejected){onFulfilled = typeof onFulfilled === 'function'?onFulfilled:x=>xonRejected = typeof onRejected === 'function'?onRejected:x=>{ throw x}if(this.state === FULFILLED){
+++ runAsynctask(()=>{onFulfilled(this.result)})}else if(this.state === REJECTED){
+++ runAsynctask(()=>{onRejected(this.result)})}else if(this.state === PENDING){this.#handlers.push({onFulfilled:()=>{
+++ runAsynctask(()=>{onFulfilled(this.result)})}, onRejected:()=>{
+++ runAsynctask(()=>{onRejected(this.result)})}})}}}// ------------- 测试代码 -------------console.log(1)const p = new TestPromise((resolve, reject) => {console.log(2)resolve(3)// reject("rejected")})p.then(res=>{console.log(res)})console.log(4)
五、链式编程
分析
promise.then().then()
promise可以一直then方法
核心:
1、then方法需要返回是支持.then调用的(promise实例)
2、根据 pending、fulfilled、rejected三种状态支持链式编程
3、在这个promise实例获取上一个then的返回值并处理{1、处理返回值2、处理异常3、处理返回promise4、处理重复引用}
1、处理返回值和处理异常
then方法中新建一个promise实例 获取返回值 处理异常
then(onFulfilled, onRejected){onFulfilled = typeof onFulfilled === 'function'?onFulfilled:x=>xonRejected = typeof onRejected === 'function'?onRejected:x=>{ throw x}// 1. 返回新Promise实例const p2 = new TestPromise((resolve, reject) => {if (this.state === FULFILLED) {runAsynctask(() => {// 2. 获取返回值try {const x = onFulfilled(this.result)// 2.1 处理返回值resolve(x)} catch (error) {// 2.2 处理异常console.log('捕获异常', error)reject(error)}})} else if (this.state === REJECTED) {runAsynctask(() => {onRejected(this.result)})} else if(this.state === PENDING){this.#handlers.push({onFulfilled:()=>{runAsynctask(()=>{onFulfilled(this.result)})}, onRejected:()=>{runAsynctask(()=>{onRejected(this.result)})}})}})return p2}// ------------- 测试代码 -------------const p = new TestPromise((resolve, reject) => {resolve("resolve")// reject("rejected")})p.then(res=>{console.log(res)// throw 'throw error'return 2}).then(res=>{console.log(res)},err=>{console.log(err)})
2、处理返回promise
如果promise.then()里面返回依旧是一个promise,这个时候需要怎么处理?
const p = new TestPromise((resolve, reject) => {resolve(1)})p.then(res => {return new TestPromise((resolve, reject) => {resolve(2)// reject('error')})}).then(res => {console.log('p2:', res) // 2}, err => {console.log('p2:', err) // err})
处理思路:
1、拿到返回值、判断是不是promise实例
2、调去这个promise实例的then方法就可以了
class TestPromise{···then(onFulfilled, onRejected){···const p2 = new TestPromise((resolve, reject) => {if (this.state === FULFILLED) {runAsynctask(() => {try {const x = onFulfilled(this.result)// 1.处理返回Promise+++ if (x instanceof TestPromise) {// 2. 调用then方法// x.then(res => console.log(res), err => console.log(err))
+++ x.then(res => resolve(res), err => reject(err))} else {resolve(x)}} catch (error) {console.log('捕获异常', error)reject(error)}})} else if (this.state === REJECTED) {···} else if(this.state === PENDING){···}})return p2}}
3、处理返回promise重复调用
const p = new Promise((resolve, reject) => {resolve("resolve")})const p2 = p.then(res=>{// throw 'throw error'return p2})p2.then(res=>{},err=>console.log('err:', err))
原生的promise会有重复调用的错误提示
思路:对promise进行比较、并抛出异常
if (this.state === FULFILLED) {runAsynctask(() => {try {const x = onFulfilled(this.result)// 1. 处理重复引用if (x === p2) {// console.log('返回了p2')// 2. 抛出错误 Chaining cycle detected for promise #<Promise>throw new TypeError('Chaining cycle detected for promise #<Promise>')}if (x instanceof TestPromise) {x.then(res => resolve(res), err => reject(err))} else {resolve(x)}} catch (error) {reject(error)}})
4、rejected状态
抽取公共方法处理promise和重复调用
// 抽取函数function resolvePromise(p2, x, resolve, reject) {if (x === p2) {throw new TypeError('Chaining cycle detected for promise #<Promise>')}if (x instanceof TestPromise) {x.then(res => resolve(res), err => reject(err))} else {resolve(x)}}
处理返回值
const p2 = new TestPromise((resolve, reject) => {if (this.state === FULFILLED) {runAsynctask(() => {try {// 获取返回值const x = onFulfilled(this.result)resolvePromise(p2, x, resolve, reject)} catch (error) {reject(error)}})} else if (this.state === REJECTED) {runAsynctask(() => {// 1、处理异常
+++ try {// 获取返回值
+++ const x = onRejected(this.result)resolvePromise(p2, x, resolve, reject)} catch (error) {reject(error)}})}
5、pending状态
} else if(this.state === PENDING){this.#handlers.push({onFulfilled:()=>{runAsynctask(()=>{try {// 获取返回值const x = onFulfilled(this.result)resolvePromise(p2, x, resolve, reject)} catch (error) {reject(error)} })}, onRejected:()=>{runAsynctask(()=>{try {// 获取返回值const x = onRejected(this.result)resolvePromise(p2, x, resolve, reject)} catch (error) {reject(error)}})}})}···// ------------- 测试代码 -------------const p = new TestPromise((resolve, reject) => {setTimeout(() => {resolve("resolve")}, 2000);})const p2 = p.then(res => {throw 'error'// return p2// return 2// return new TestPromise((resolve, reject) => {// reject('Promise-error')// })})p2.then(res=>{return console.log('err:', err)}, err => {console.log('p2-err:', err)})
六、实例方法-catch -finally
实例方法-catch
我们先看下官网对catch的讲述
由此我们可以得出:
// catch是什么? 是语法糖!!!
new Promise(() => {
}).catch(() => {
})
// 等同于
new Promise(() => {
}).then(null, () => {
})
那么这个实力方法时处理以下两点1、then中没有写err函数const p = new TestPromise((resolve, reject) => {reject('reject-error')})p.then(res => {console.log('res:', res)}).catch(err => {console.log('err:', err)})2、创建函数时的异常 没有resolve reject
const p = new TestPromise((resolve, reject) => {throw 'throw-error'})
实现:
1、在类then方法下面新建catch方法
/**
* catch方法* 1. 内部调用then方法* */catch(onRejected) {// 1. 内部调用then方法return this.then(undefined, onRejected)}2、在constructor构造函数中 处理异常constructor(func) {// pending->fulfilledconst resolve = (result) => {if (this.state === PENDING) {this.state = FULFILLEDthis.result = resultthis.#handlers.forEach(({ onFulfilled }) => {onFulfilled(this.result)})}}// pending->rejectedconst reject = (result) => {if (this.state === PENDING) {this.state = REJECTEDthis.result = resultthis.#handlers.forEach(({ onRejected }) => {onRejected(this.result)})}}// 2. 处理异常
+++ try {func(resolve, reject)} catch (error) {// console.log('error:', error)reject(error)}}
实例方法-finally
以下时官网promise对finnally的介绍
由此可知,其内部调取then(onFinally, onFinally)
/**
* finally方法* 1. 内部调用then方法* */
finally(onFinally) {return this.then(onFinally, onFinally)
}
七、静态方法-resolve -reject-race-all-allSettled-any
注意:静态的是指向类自身,而不是指向实例对象,主要是归属不同,这是静态属性,静态方法的核心
也就是说类可以访问到,但实例对象访问不到、继承类也可以访问到
静态方法-resolve
官网介绍:
由此可知:resolve是把一个值转换为promise实例,如果本身就是promise实例那么直接返回
实现:
/*** 静态方法-resolve* 1. 判断传入值* 2.1 Promise直接返回* 2.2 转为Promise并返回(fulfilled状态)* */static resolve(value) {// 1. 判断传入值if (value instanceof TestPromise) {// 2.1 Promise直接返回return value}// 2.2 转为Promise并返回(fulfilled状态)return new TestPromise((resolve) => {resolve(value)})}
测试
// ------------- 测试代码 手写Promise -------------TestPromise.resolve(new TestPromise((resolve, reject) => {resolve('resolve')// reject('reject')// throw 'error'})).then(res => {console.log('res:', res)}, err => {console.log('err:', err)})TestPromise.resolve('hahah').then(res => {console.log(res)})
静态方法-reject
由此可知静态方法-reject为传递一个reject的promise对象,实现:
/*** 静态方法-reject* 1. 返回rejected状态的Promise* */static reject(value) {// 1. 返回rejected状态的Promisereturn new TestPromise((undefined, reject) => {reject(value)})}
// 测试:
// ------------- 测试代码 手写Promise -------------TestPromise.reject('error').catch(res => {console.log(res)})
静态方法-race
从官网可知,race接收一个promise对象数组,返回第一个最快执行完的promise,无论它是rejected还是fuilled
注意:如果传递的不是promise,会把值默认转换成promise对象,并执行resolve
注意:如果是传递的不是数组,会报以下错误
/*** 静态方法-race* 1. 返回Promise* 2. 判断是否为数组 错误信息:Argument is not iterable* 3. 等待第一个敲定* */static race(promises) {// 1. 返回Promisereturn new TestPromise((resolve, reject) => {// 2. 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError('Argument is not iterable'))}// 3. 等待第一个敲定promises.forEach(p => {// p.thenTestPromise.resolve(p).then(res => { resolve(res) }, err => { reject(err) })})})}
注意:因为返回时一个promise对象,所以 promises.forEach数组遍历中,
最快的一个会调用resolve或reject,
只要其中一个执行,promises对象便不会在执行其他的
// ------------- 测试代码 手写Promise -------------const p1 = new TestPromise((resolve, reject) => {setTimeout(() => {resolve(1)}, 1000)})const p2 = new TestPromise((resolve, reject) => {setTimeout(() => {reject(2)}, 2000)})// TestPromise.race([p1, p2]).then((res) => {TestPromise.race([p1, p2, 'hahah']).then((res) => {// TestPromise.race().then((res) => {console.log('res:', res)}, err => {console.log('err:', err)})
静态方法-all
分析:
promise.all([promise1,promise2,promise3])
all方法接收一个promise数组,
如果都是resolve 并返回传入数组顺序的promise的res数组
当有rejected时,返回第一个rejected
思路:
/*** 静态方法-all* 1. 返回Promise实例* 2. 判断是否为数组 错误信息:Argument is not iterable* 3. 空数组直接兑现* 4. 处理全部兑现* 4.1 记录结果* 4.2 判断全部兑现* 5. 处理第一个拒绝* */static all(promises){// 1. 返回Promisereturn new TestPromise((resolve, reject) => {// 2. 判断是否为数组if (!Array.isArray(promises)) {return reject(new TypeError('Argument is not iterable'))}// 3. 空数组直接兑现promises.length === 0 && resolve(promises)// 4. 处理全部兑现 // 思路(promise异步执行顺序,返回数组和原来的传入数组顺序可能不同索引 // 所以我们可以根据传入数组的索引值对最终数组进行数组排序)// 4.1 记录结果const results = []let count = 0promises.forEach((p, index)=>{TestPromise.resolve(p).then(res=>{results[index] = res// 4.2 判断全部兑现// 为什么不能用 results.length进行判断,因为如果第一个执行完返回的是第三项results[3] = res 当前数组情况是[ , , res]// 我们用count次数进行判断count++count===promises.length && resolve(results)}, err=>{// 5. 处理第一个拒绝reject(err)})})})}// ------------- 测试代码 手写Promise -------------const p1 = TestPromise.resolve(1)const p2 = new TestPromise((resolve, reject) => {setTimeout(() => {resolve(2)// reject('error')}, 1000)})const p3 = 3const p4 = new TestPromise((resolve, reject) => {setTimeout(() => {// resolve(4)reject('error-1234')}, 2000)})TestPromise.all([p1, p2, p3, p4]).then(res => {// TestPromise.all().then(res => {// TestPromise.all([]).then(res => {console.log('res:', res)}, err => {console.log('err:', err)})
静态方法-allSettled
分析:
我们promise测试一下
由此发现:它和all方法类似,依旧是等待所有的promise敲定返回,顺序也是传入顺序,
但不是第一个rejected抛出,而是全部执行完后以 status:fulfilled,value:value返回resolve,以status:rejected,reason:value返回rejected的数组
实现:
/*** 静态方法-allSettled* 1. 返回Promise* 2. 数组判断 错误信息: Argument is not iterable* 3. 为空直接敲定* 4. 等待全部敲定* 4.1 记录结果* 4.2 处理兑现{status:'fulfilled',value:''}* 4.3 处理拒绝{status:'rejected',reason:''}* */static allSettled(promises) {// 1. 返回Promisereturn new TestPromise((resolve, reject) => {// 2. 数组判断if (!Array.isArray(promises)) {return reject(new TypeError('Argument is not iterable'))}// 3. 为空直接敲定promises.length === 0 && resolve(promises)// 4. 等待全部敲定// 4.1 记录结果const results = []let count = 0promises.forEach((p, index) => {TestPromise.resolve(p).then(res => {// 4.2 处理兑现{status:'fulfilled',value:''}results[index] = { status: FULFILLED, value: res }count++count === promises.length && resolve(results)}, err => {// 4.3 处理拒绝{status:'rejected',reason:''}results[index] = { status: REJECTED, reason: err }count++count === promises.length && resolve(results)})})})}
// ------------- 测试代码 手写Promise -------------const p1 = TestPromise.resolve(1)const p2 = 2const p3 = new TestPromise((resolve, reject) => {setTimeout(() => {reject(3)}, 1000)})TestPromise.allSettled([p1, p2, p3]).then(res => {// TestPromise.allSettled().then(res => {// TestPromise.allSettled([]).then(res => {console.log('res:', res)}, err => {console.log('err:', err)})
静态方法-any
由此可知,any是接收一个promise数组(可以是常量),如果存在一个成功,则直接返回第一个成功的resolve
如果没有成功的,则返回所有的拒绝原因(与传入顺序一致)
注意:空值和空数组都会报错
实现
/*** 静态方法-any* 1. 返回Promise,数组判断 错误信息: Argument is not iterable* 2. 空数组直接拒绝 AggregateError([错误原因1..],All promises were rejected)* 3. 等待结果* 3.1 第一个兑现* 3.2 全部拒绝*/static any(promises) {// 1. 返回Promise,数组判断return new TestPromise((resolve, reject) => {if (!Array.isArray(promises)) {return reject(new TypeError('Argument is not iterable'))}// 2. 空数组直接拒绝promises.length === 0 && reject(new AggregateError(promises, 'All promises were rejected'))// 3. 等待结果const errors = []let count = 0promises.forEach((p, index) => {TestPromise.resolve(p).then(res => {// 3.1 第一个兑现resolve(res)}, err => {// 3.2 全部拒绝errors[index] = errcount++count === promises.length && reject(new AggregateError(errors, 'All promises were rejected'))})})})}
// ------------- 测试代码 手写Promise -------------const p1 = new TestPromise((resolve, reject) => {setTimeout(() => {reject(1)}, 2000)})// const p2 = 2const p2 = TestPromise.reject(2)const p3 = new TestPromise((resolve, reject) => {setTimeout(() => {// resolve(3)reject(3)}, 1000)})TestPromise.any([p1, p2, p3]).then(res => {// TestPromise.any().then(res => {// TestPromise.any([]).then(res => {console.log('res:', res)}, err => {console.dir(err)})