从0手写Promise(一步步深刻理解)

本文从基础知识准备出发,详细讲解函数对象与实例对象、同步与异步回调函数及JS的错误处理。接着深入探讨Promise的原理、使用场景及其解决的回调地狱问题。通过实例介绍如何使用Promise,包括其构造函数、then、catch方法等API,以及Promise状态改变和回调函数调用的顺序。最后,文章指导读者尝试手写Promise,以深入理解其工作机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、基础知识准备

1、函数对象与实例对象

函数对象:将函数作为对象使用时,简称为函数对象

实例对象:new函数产生的对象,简称为对象

function Fn(){    //Fn函数
}
const fn = new Fn()    //Fn是构造函数   fn是实例对象(对象)
console.log(Fn.prototype)    //Fn是函数对象
//记忆:Fn放在,括号左边是函数,点的左边是对象

Fn.bind({})     //只有函数对象才有 call()、apply()、bind()方法    
                //解释:这里所有函数都是Fn的实例,实例会去找原型对象上的方法
Fn.call({})     //Fn是函数对象

$('#test')    //jQuery函数
$.get('/test')    //jQuery函数对象

2、两种类型的回调函数

首先我们需要清楚这几点:

  • 回调函数:函数可以作为参数在另一个函数中调用
  • 同步回调函数
const arr = [1, 3, 5]
arr.forEach(item => {    //遍历回调:每取出一个元素,调用一次    同步回调函数
    console.log(item)    //不会放入队列  一上来就执行
})
console.log('forEach()之后')
//1
//3
//5
//forEach()之后
  • 异步回调函数
setTimeout(() => {    //异步回调函数  会放入队列中将来执行
    console.log('后执行')
},0)
console.log('先执行')
//先执行
//后执行

(1)同步回调

理解:立即执行,完全执行完了才会结束,不会放入回调队列中

例子:数组遍历相关的回调函数、Promise的excutor函数

(2)异步回调

理解:不会立即执行,会放入回调队列中将来执行

例子:定时器回调、ajax回调、Promise的成功/失败回调

判断回调是同步还是异步:打印输出一下

3、JS的error处理

参考官方文档:JS-error

(1)错误类型

  • Error:所有错误的父类型

常见内置错误:

  • ReferenceError:引用的变量不存在
console.log(a)    //ReferenceError: a is not defined
console.log('......')    //没有捕获错误,下面的代码不会执行

  • TypeError:数据类型不正确的错误
let b
console.log(b.xxx)    //TypeError: Cannot read property 'xxx' of undefined

let b
b.xxx()    //TypeError: Cannot read property 'xxx' of undefined

let b = {}
b.xxx()    //TypeError: b.xxx is not a function
  • RangeError:数据值不在其所允许的范围
function fn() {
    fn()
}
fn()

  • SyntaxError:语法错误
const c =""""    //SyntaxError: Unexpected string

(2)错误处理

  • 捕获错误:try...catch
try {
    let d 
    console.log(d.xxx)
} catch (error) {
    console.log(error)    //error中包含message和stack属性
}

try {
    let d 
    console.log(d.xxx)
} catch (error) {
    console.log(error.message)    
    console.log(error.stack)
}
console.log('出错之后')

  • 抛出错误:throw error
function something() {
    if(Date.now()%2===1) {
        console.log('当前时间为奇数,可以执行任务')
    } else {    //如果时间为偶数,抛出异常,由调用者来处理
        throw new Error('当前时间为偶数,无法执行任务') 
    }
}
//捕获处理异常
try {
    something()
} catch (error) {
    alert(error.message)    //调用者决定如何处理异常
}

(3)错误对象

  • message属性:错误相关信息
  • stack属性:函数调用栈记录信息

二、Promise的理解和使用

1、Promise是什么?

(1)理解

抽象表达:Promise是JS中进行异步编程的解决方案(旧方案--纯回调)

具体表达:

  • 语法上来说,Promise是一个构造函数
  • 功能上来说,Promise对象用来封装一个异步操作,并可以获取其结果

(2)Promise的状态改变

Promise有三种状态:pending(进行中,未确定)、resolved(已成功)、rejected(已失败)

状态改变:

  • pending变为resolved
  • pending变为rejected

注意:

  • 只有两种状态改变,且一个Promise对象只能改变一次
  • 无论成功失败,都会有一个结果数据,成功的结果数据为value,失败为reason

(3)Promise的基本流程

(4)Promise的基本使用

// 1 创建一个新的promise对象
const p = new Promise((resolve, reject) => {    //执行器函数excutor    同步回调
    // 2 异步操作任务
    setTimeout(() => {
        const time = Date.now()    //偶数成功,否则失败
        // 3.1 成功 调用resolve(value)
        if(time%2 == 0) {
            resolve('成功的数据,time=' + time)
        } else {
        // 3.2 失败 调用reject(reason)
            reject('失败的数据,time=' + time)
        }
    },1000)
})
p.then(
    value => {    //接收得到成功的value数据     onResolved
        console.log('成功的回调',value)
    },
    reason => {    //接收得到失败的reason数据   onRejected
        console.log('失败的回调',reason)
    }

)

2、为什么要用Promise?

(1)指定回调函数的方式更加灵活

旧方案:必须在启动异步任务前指定

Promise:启动异步任务=>返回Promise对象=>给Promise对象绑定回调函数(可以在异步任务结束后指定/多个)

(2)支持链式调用,解决回调地狱问题

1、什么是回调地狱?

回调函数嵌套调用,外部回调函数异步执行的结果是嵌套的回调函数执行的条件。

----回调函数地狱涉及多个串联执行的异步操作,下一个异步任务执行是以上一个异步任务的结果为条件的

2、回调地狱的特点?

不便于阅读,不便于异常处理

3、解决方案?

Promise链式调用(无回调函数嵌套问题)

----代码从上往下写,非常易于阅读。异步任务从上往下执行,异常统一最后处理,非常方便。不过会有异常传透问题(错误传递):前面的异常会一直往下传递,最后才处理

但是还有回调函数没有解决(程序员开发角度看)

4、终极解决方案?

async/await

async function request() {
    try {
        const result = await doSomething()
        const newResult = await doSomethingElse(result)
        const finalResult = await doThirdthing(newResult)
        console.log('Got the final result' + finalResult)
    } catch (error) {
        failureCallback(error)
    }
}

3、如何使用Promise?

(1)API(语法/前后台交互的接口)

1. Promise构造函数:Promise(excutor) {}

  • excutor函数:执行器 (resolve, reject) =>{}
  • resolve函数:内部定义成功时调用 value =>{}
  • reject函数:内部定义失败时调用 reason =>{}

说明:excutor会在Promise内部立即同步回调,异步操作在执行器中执行

new Promise((resolve, reject) => {
    setTimeout(() => {
            resolve('成功的数据')
            //reject('失败的数据')  只会执行第一个,因为promise状态只会改变一次,且不能变
        }
    },1000)
}).then(
    value => {
        console.log('onResolved()1',value)
    }
).catch(
    reason => {
        console.log('onRejected()1',reason)
    }
)

2. Promise.prototype.then方法:(onResolved, onRejected) => {}

  • onResolved函数:成功的回调函数 (value) => {} 
  • onRejected函数:失败的回调函数 (reason) => {} 

说明:指定用于得到成功value的成功回调和用于得到失败reason的失败回调    返回一个新的promise对象

3. Promise.prototype.catch方法:(onRejected) => {}

  • onRejected函数:失败的回调函数 (reason) => {} 

说明:then()的语法糖,相当于:then(undefined, onRejected) 

4. Promise.resolve方法:(value) => {}

  • value:成功的数据或promise对象

说明:返回一个成功/失败的promise对象

//产生一个成功值为1的promise对象
//方法一
const p1 = new Promise((resolve,reject) => {
    resolve(1)
})
//方法二
const p2 = Promise.resolve(2)

const p3 = Promise.reject(3)
p1.then(value => {console.log(value)})
p2.then(value => {console.log(value)})
p3.catch(reason => {console.log(reason)})

5. Promise.reject方法:(reason) => {}

  • reason:失败的原因

说明:返回一个失败的promise对象

6. Promise.all方法:(promises) => {}

  • promises:包含n个promise的数组 

说明:返回一个新的promise,只有所有的promise都成功才成功,只要有一个失败了就直接失败

const pAll = Promise.all([p1, p2, p3])
pAll.then(
    value => {
        console.log('all onResolved()', value)
    },
    reason => {
        console.log('all onRejected()', reason)
    }
)
// all onRejected() 3
//将p3去掉
const pAll = Promise.all([p1, p2])
pAll.then(
    values => {
        console.log('all onResolved()', values)
    },
    reason => {
        console.log('all onRejected()', reason)
    }
)
// all onResolved() [1, 2] 
//注意:这里打印出的数组元素位置跟all([p1, p2])是一一对应的

7. Promise.race方法:(promises) => {}

  • promises:包含n个promise的数组 

说明:返回一个新的promise,第一个完成的promise的结果状态就是最终的结果状态

//这里修改下p1,给它设置个延时,这样第一个完成的promise是p2
const p1 = new Promise(() => {
    setTimeout(() => {
        resolve(1)
    },1000)
})
const p2 = Promise.resolve(2)
const p3 = Promise.reject(3)
const pRace = Promise.race([p1, p2, p3])
pRace.then(
    value => {
        console.log('race onResolved()', value)
    },
    reason => {
        console.log('race onRejected()', reason)
    }
)
// race onResolved() 2

(2)Promise的几个关键问题

1. 如何改变Promise的状态?

  • resolve(value):如果当前是pending,就会变成resolved
  • reject(reason):如果当前是pending,就会变成rejected
  • 抛出异常:如果当前是pending,就会变成rejected
const p = new Promise((resolve, reject) => {
    //resolve(1)    //promise变为resolved状态
    //reject(2)     //promise变为rejected状态
    //throw new Error('出错了')    //抛出异常 promise变为rejected状态 reason为抛出的error
    throw 3    //抛出异常 promise变为rejected状态 reason为抛出的 3
})
p.then(
    value => {}
    reason => {console.log('reason',reason)}    //reason 3
)

2. 一个Promise指定多个成功/失败回调函数,都会调用吗?

当Promise改为对应状态时都会调用

const p = new Promise((resolve, reject) => {
    throw 3
})
p.then(
    value => {}
    reason => {console.log('reason1',reason)}    
)
p.then(
    value => {}
    reason => {console.log('reason2',reason)}    
)
//reason1 3
//reason2 3

3. 改变Promise状态和指定回调函数,谁先谁后?

都有可能,正常情况下先指定回调再改变状态,但也可以先改变状态再指定回调

//常规:先指定回调,后改变状态
new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1)    //后改变的状态(同时指定数据),异步执行回调函数
    },1000);
}).then(    //先指定回调函数,并保存当前指定的回调函数            
    value => {}
    reason => {console.log('reason',reason)}
)

如何先改状态再指定回调?

  • 在执行器中直接调用resolve()/reject()
new Promise((resolve, reject) => {
    resolve(1)    //先改变的状态(同时指定数据)
}).then(    //后指定回调函数,异步执行回调函数            
    value => {console.log('value',value)}
    reason => {console.log('reason',reason)}
)
console.log('------')
//注意:resolve和then都是同步执行    value/reason异步执行
//打印结果:
//------
//value 1
  • 延迟更长时间才调用then()
const p = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1)
    },1000);
})
setTimeout(() => {
    p.then(
        value => {console.log('value',value)}   
        reason => {console.log('reason',reason)}      
    )
},2000);

什么时候才能得到数据?

  • 如果先指定回调,当状态发生改变时,回调函数就会调用,得到数据
  • 如果先改变状态,当指定回调时,回调函数就会调用,得到数据

4. promise.then()返回的新promise的结果状态由什么决定?(重点)

简单表达:由then()指定的回调函数执行的结果决定

详细表达:

  • 如果抛出异常,新promise变为rejected,reason为抛出的异常
  • 如果返回的是非promise的任意值,新promise变为resolved,value为返回的值
  • 如果返回的是另一个新promise,这个promise的结果就会成为新promise的结果
//执行成功的情况
new Promise((resolve, reject) => {
    resolve(1)
    //reject(1)   
}).then(            
    value => {
        console.log('onResolved1()',value)
        // 1. 这里如果没有写return返回值  则默认为 return undefined
        // 2. return 2
        // 3. return Promise.resolve(3)
        // 4. return Promise.reject(4)
        // 5. throw 5
    }
    reason => {console.log('onRejected1()',reason)}
).then(            //新promise的状态由上一个执行的回调函数value/reason决定
    value => {console.log('onResolved2()',value)}
    reason => {console.log('onRejected2()',reason)}
)
//第一个then打印:onResolved1() 1
//第二个then打印:1. onResolved2() undefined
//               2. onResolved2() 2
//               3. onResolved2() 3
//               4. onRejected2() 4   
//               5. onRejected2() 5       
//执行失败的情况
new Promise((resolve, reject) => {
    reject(1)   
}).then(            
    value => {console.log('onResolved1()',value)}
    reason => {
        console.log('onRejected1()',reason)
        // 1. 这里如果没有写return返回值  则默认为 return undefined
        //其他返回结果情况同上
    }
).then(    //上一个then中执行的回调函数reason没有报错,且return undefined  
           //说明这里的then返回的promise成功,且成功的值为undefined
    value => {console.log('onResolved2()',value)}
    reason => {console.log('onRejected2()',reason)}
)
//onRejected1() 1
//onResolved2() undefined

//注意:第一个then执行失败的回调函数,后面的then不一定也执行失败的回调函数,而取决于上一个then执行的回调函数的结果

5. promise如何串联多个操作任务?

  • promise的then()返回一个新的promise,可以看成then()的链式调用
  • 通过then的链式调用串连多个同步/异步任务
new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('执行任务1(异步)')
        resolve(1)
    },1000);  
}).then(            
    value => {
        console.log('任务1的结果:',value)
        console.log('执行任务2(同步)')
        return 2
    }
).then(
    value => {
        console.log('任务2的结果:',value)
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('执行任务3(异步)')
                resolve(3)
            },1000);
        })
    }
).then(            
    value => {
        console.log('任务3的结果:',value)
    }
)
//打印结果:
//执行任务1(异步)
//任务1的结果:1
//执行任务2(同步)
//任务2的结果:2
//执行任务3(异步)
//任务3的结果:3

6. Promise异常传透?

  • 当使用promise的链式调用时,可以在最后指定失败的回调
  • 前面任何操作出了异常,都会传到最后失败的回调中处理
new Promise((resolve, reject) => {
    reject(1)  
}).then(            
    value => {
        console.log('onResolved1()',value)
        return 2
    },
    // reason => {throw reason}
).then(
    value => {
        console.log('onResolved2()',value)
        return 3
    },
    // reason => {throw reason}
).then(            
    value => {
        console.log('onResolved3()',value)
    },
    // reason => {throw reason}
).catch(
    reason => {
        console.log('onRejected1()', reason)
    }
)
//前面的then都失败,才会传递到下面执行catch
//失败状态: 抛出异常  reason => {throw reason}  默认情况
//          返回失败promise   reason => Promise.reject(reason)

7. 中断promise链?

  • 当使用promise的链式调用时,在中间中断,不再调用后面的回调函数
  • 办法:在回调函数中返回一个pending状态的promise状态
new Promise((resolve, reject) => {
    reject(1)  
}).then(            
    value => {
        console.log('onResolved1()',value)
        return 2
    },
    // reason => {throw reason}
).then(
    value => {
        console.log('onResolved2()',value)
        return 3
    },
    reason => Promise.reject(reason)

).catch(
    reason => {
        console.log('onRejected1()', reason)  // onRejected1() 1
    }
    //1.若这里什么都不加 则下一个then会进入成功回调 打印 onResolved3() undefined
    //2.若这里加上 throw reason 或 return Promise.reject(reason) 则下一个then进入失败的回调
    //    下面会打印 onRejected2() 1
    return new Promise(() => {})    //返回一个pending的promise(决定下一个then的状态) 中断promise链
).then(            
    value => {
        console.log('onResolved3()',value)
    },
    reason => {
        console.log('onRejected2()', reason)
    }
)
//onRejected1() 1

三、手写Promise

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值