前言
目标:
封装一个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 reject
const resolve = (result)=>{
// TODO
console.log("resolve",result)
}
const reject = (result)=>{
// TODO
console.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 = undefined
constructor(func){
// 改状态: pending->fulfilled
// 记录原因
const resolve = (result)=>{
// 加判断状态不可逆
if(this.state===PENDING){
this.state = FULFILLED
this.result = result
}
}
// 改状态: pending->rejected
// 记录原因
const reject = (result)=>{
if(this.state===PENDING){
this.state = REJECTED
this.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 = FULFILLED
this.result = result
// 3、执行成功的回调
this.#handlers.forEach(({onFulfilled})=>{
onFulfilled(this.result)
})
}
}
const reject = (result)=>{
if(this.state===PENDING){
this.state = REJECTED
this.result = result
// 3、执行失败的回调
this.#handlers.forEach(({onRejected})=>{
onRejected(this.result)
})
}
}
func(resolve,reject)
}
then(onFulfilled, onRejected){
onFulfilled = typeof onFulfilled === 'function'?onFulfilled:x=>x
onRejected = 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、核心API
2、函数封装
}
核心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=>x
onRejected = 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、处理返回promise
4、处理重复引用
}
1、处理返回值和处理异常
then方法中新建一个promise实例 获取返回值 处理异常
then(onFulfilled, onRejected){
onFulfilled = typeof onFulfilled === 'function'?onFulfilled:x=>x
onRejected = 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->fulfilled
const resolve = (result) => {
if (this.state === PENDING) {
this.state = FULFILLED
this.result = result
this.#handlers.forEach(({ onFulfilled }) => {
onFulfilled(this.result)
})
}
}
// pending->rejected
const reject = (result) => {
if (this.state === PENDING) {
this.state = REJECTED
this.result = result
this.#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状态的Promise
return 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. 返回Promise
return new TestPromise((resolve, reject) => {
// 2. 判断是否为数组
if (!Array.isArray(promises)) {
return reject(new TypeError('Argument is not iterable'))
}
// 3. 等待第一个敲定
promises.forEach(p => {
// p.then
TestPromise.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. 返回Promise
return 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 = 0
promises.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 = 3
const 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. 返回Promise
return 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 = 0
promises.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 = 2
const 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 = 0
promises.forEach((p, index) => {
TestPromise.resolve(p).then(res => {
// 3.1 第一个兑现
resolve(res)
}, err => {
// 3.2 全部拒绝
errors[index] = err
count++
count === promises.length && reject(new AggregateError(errors, 'All promises were rejected'))
})
})
})
}
// ------------- 测试代码 手写Promise -------------
const p1 = new TestPromise((resolve, reject) => {
setTimeout(() => {
reject(1)
}, 2000)
})
// const p2 = 2
const 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)
})