js三座大山
一:函数式编程
js三座大山之函数1
js三座大山之函数2-作用域与动态this
二:面向对象编程
js三座大山之对象,继承,类,原型链
三:异步编程:
js三座大山之异步一单线程,event loope,宏任务&微任务
js三座大山之异步二异步方案
js三座大山之异步三promise本质
js三座大山之异步四-Promise的同步调用消除异步的传染性
js三座大山之异步五基于异步的js性能优化
js三座大山之异步六实现微任务的N种方式
js三座大山之异步七实现宏任务的N种方式
当前主流的js异步编程方案中 都是基于promise的。promise最大的优点就是统一了异步调用的api,所有异步操作都可以使用相同的模式来处理问题。使用链式调用,避免了回调地狱。本质上和回调的方案并没有区别。promise本质是一个存储回调的容器
和 js三座大山之异步二异步方案 中的基于事件的发布订阅
有点类似。
关于promise的基础使用请看
阮一峰promise
mdn-Promise
用法范式:
promise的用法可以分为两大步骤。
- 创建promise对象并传入执行器&同步运行执行器
const execute = (onResolve, onReject) => {
setTimeout(() => {
onResolve(123);
}, 2000);
};
const p = new Promise(execute);
- 注册异步回调函数,等待执行器返回结果,调用注册的函数。
// 成功的回调
p.then((data)=>{
console.log('第一个成功的异步回调 异步结果:', data)
})
// 失败的回调
p.catch((reason)=>{
console.log('第一个失败的异步回调 异步结果:', reason)
})
// 成功|失败都被执行的回调
p.finally(()=>{
console.log('第一个finally异步回调')
})
容器结构:promise本质是一个存储回调的容器
那么这容器里面都有什么呐?
1.状态state
promsie内部维护了一个状态 可以从pendding->fulfilled || pendding->rejected。仅有这两种变化。且一旦状态改变,就不会再变,任何时候都可以得到这个结果。
这一点promise与基于事件的发布订阅不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
2.执行结果value
当执行器执行成功后需要设置结果 执行失败后需要设置失败原因 这都是运行的结果被保存在promise内部。
3.回调函数列表handles
有成功的回调函数 通过then,finally注册的 可以是多个
有失败的回调函数 通过then,catch,finally注册的 可以是多个
4.钩子函数onResolve/onReject
外部在promise构造函数中需要注入一个执行器 promise需要对外提供一个钩子 当执行器获取到异步结果后 需要通过钩子改变promise的状态 设置结果 触发回调函数执行。
5.api
例如接受注册回调函数的各种api then catch finally 以及一些静态race all resolve reject等。
promise特点
链式调用
promise的这些api 无论是静态的还是原型上的 都有一个特点即支持链式调用
。实现链式调用的原理也很简单,每次都返回一个新的promise实例
即可。
值穿透
promise的值通过钩子函数onResolve/onReject设置 或者通过注册的回调函数获取结果
,如果处理函数返回一个非Promise值,那么这个值会被直接传递到下一个
举个例子
例子1:
const execute = (onResolve, onReject) => {
setTimeout(() => {
onResolve(123);
}, 2000);
};
const p = new Promise(execute);
p.then(result => {
console.log('result:', result)
return 456;
}).catch(error => {
console.log('error:', error)
}).finally(() => {
console.log('finally')
}).then(res=>{
console.log('res:', res)
});
console.log('同步代码')
下面逐行分析下:
- js解析执行脚本代码,初始化执行器execute。
- 初始化第一个promise实例 将execute传入构造函数 execute被同步执行 遇到异步api计时器 加入到事件循环中
- p执行then 容器内部注册一个成功状态的回调函数 then执行完毕返回第二个promise。
- 第二个promise执行catch方法 内部被注册一个失败的回调函数 catch执行完毕 返回第三个promise实例。
- 第三个promise执行finally方法 内部注册成功和失败都要执行的回调函数 finally执行完毕 返回第四个promise实例。
- 第四个promise执行then方法 内部被注册一个成功的回调 返回第五个promise实例。
- 打印log 同步代码。
到这里同步代码执行完毕 实例化了5个promise。
下面是异步部分:
- 计时器运行结束 回调函数推入到事件队列 执行栈执行回调函数。
- resolve(123)钩子执行 触发第一个promise状态改变 pendding->fulfilled。value被设置为123。将value=123作为参数,依次执行容器内的成功回调函数。
- 成功回调函数执行结束 获取返回值456。触发第二个promise状态改变pendding->fulfilled,将第二个Promise的value设置为456。将value=456作为参数,依次执行第二个Promise内的成功回调函数。
- 第二个Promise内的成功回调函数为空。将value=456透传给第三个promise。同时第三个promise状态改变pendding->fulfilled。因为第三个promise内部的函数是通过finally注册的,所以在执行时并不会将value作为参数,仅仅是依次执行第三个Promise内的成功回调函数。
- 第三个Promise内的成功回调函数执行完毕,触发第四个promise状态改变pendding->fulfilled,同时将value=456透传给第四个promise. 将value=456作为参数,依次执行第四个Promise内的成功回调函数。
- 执行第四个Promise内的成功回调函数,获取返回值undefined,设置第五个promsie的value=undefined,触发第五个promise状态改变pendding->fulfilled。
promise值透传规律
- 通过
钩子设置onResolve/onReject
显示设置 例如上面的onResolve(123) - 如果
回调函数有返回值
则用当前的返回值设置下一个promsie的value 例如上面的返回值456 - 当没有对应的回调函数时(finally注册的相当于没有)透传当前的value。 例如第二个promise到第三个。
例子二:
const execute = (onResolve, onReject) => {
setTimeout(() => {
onReject('失败')
}, 2000);
};
const p = new Promise(execute);
p.then((data)=>{
console.log('成功的异步回调 异步结果:', data)
});
p.catch((reason)=>{
console.log('失败的异步回调 异步结果:', reason)
return 'fail'
});
p.finally(()=>{
console.log('finally异步回调')
});
问题:运行结果是什么
打印失败和成功的回调应该不难理解 因为promsie被reject了。
但是为什么还会有报错呐?我明明已经catch了啊~
分析下:
- 首先同步代码执行结束 生成了
4个promise实例
。第一个promise内部被注册了三组回调函数
。 - 计时器运行结束 onReject钩子触发
promise1
状态改变pendding->rejected
. promise的value='失败'。然后依次执行对应的回调列表。 - 执行第一个回调列表 发现没有失败对应的回调函数
所以透传当前的promise的值和状态给下一个promsie
。所以promise2
的状态改变pendding->rejected
. promise的value='失败'。 - 执行第二个回调列表 有失败的处理函数 将promsie的value='失败'作为参数 执行此函数。得到返回结果’fail‘不是一个promise 因此将结果作为
promise3
的值 并更新状态pendding->fulfilled
. - 执行第三个回调列表 有失败的处理函数 但是这个函数是通过finally注册的 因此仅执行当前函数。并将当前promise的结果通过钩子onReject透传给
promsie4
。 所以第四个promsie状态pendding->rejected
,value='失败'。
- 第一次循环结束。
- 上次循环中3改变了
promise2
的状态为rejected,这次循环中依次执行对应的回调列表。因为promise2
没有回调列表 所以rejected状态,再次向上抛出。抛出第一个错误异常uncaught. - 上次循环中
promise3
状态为fulfilled,内部没有回调列表 所以不执行。 - 上次循环中
promise4
状态为rejected,依次执行对应的回调列表,因为promise4
没有回调列表 所以rejected状态,再次向上抛出。抛出第二个错误异常uncaught.
例子三
const execute = (resolve, reject) => {
setTimeout(() => {
resolve("123");
}, 2000);
};
const p = new Promise(execute);
p.then((data) => {
console.log("1111111");
})
.finally(() => {
console.log("33333333");
})
.finally(() => {
console.log("55555555");
});
p.finally(() => {
console.log("2222222");
}).finally(() => {
console.log("4444444");
});
输出结果:
1111111
2222222
33333333
4444444
55555555
代码实现:
手动实现了一版本promise,手写promise。有兴趣的同学可以看下~
一个思考题:
请问输出什么? 评论区告诉我答案 !
Promise.resolve().then(()=>{
console.log(0)
return Promise.resolve(4);
}).then((res)=>{
console.log(res)
}).then(()=>{
console.log(6)
})
Promise.resolve().then(()=>{
console.log(1)
}).then(()=>{
console.log(2)
}).then(()=>{
console.log(3)
}).then(()=>{
console.log(5)
})