Promise解决的是异步编码⻛格的问题,而不是一些其他的问题
异步编程的问题:代码逻辑不连续
首先我们来回顾下JavaScript的异步编程模型,你应该已经非常熟悉⻚面的事件循环系统了,也知道⻚面中任务都是执行在主线程之上的,相对于⻚面来说,主线程就是它整个的世界,所以在执行一项耗时的任务时,比如下载网络文件任务、获取摄像头等设备信息任务,这些任务都会放到⻚面主线程之外的进程或者线程中去执行,这样就避免了耗时任务“霸占”⻚面主线程的情况。你可以结合下图来看看这个处理过程:
Web⻚面的单线程架构决定了异步回调,而异步回调影响到了我们的编码方式,到底是如何影响的呢?
回调地狱
XFetch(makeRequest('https://time.geekbang.org/?category'),
function resolve(response) {
console.log(response)
XFetch(makeRequest('https://time.geekbang.org/column'),
function resolve(response) {
console.log(response)
XFetch(makeRequest('https://time.geekbang.org')
function resolve(response) {
console.log(response)
}, function reject(e) {
console.log(e)
})
}, function reject(e) {
console.log(e)
})
}, function reject(e) {
console.log(e)
})
问题1、嵌套过多 问题二、每个都需要处理一次异常
如何让请求扁平化,错误只处理一次?
Promise
Promise:消灭嵌套调用和多次错误处理
var x1 = XFetch(makeRequest('https://time.geekbang.org/?category'))
var x2 = x1.then(value => {
console.log(value)
return XFetch(makeRequest('https://www.geekbang.org/column'))
})
var x3 = x2.then(value => {
console.log(value)
})
x3.catch(e => {
})
解决了潜逃地狱+所有错误就处理一次行了。
内部Promise 返回到外部实现扁平化。
现在我们知道了Promise通过回调函数延迟绑定和回调函数返回值穿透的技术,解决了循环嵌套
Promise是怎么处理异常的
之所以可以使用最后一个对象来捕获所有异常,是因为Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被onReject函数处理或catch语句捕获为止。具备了这样“冒泡”的特性后,就不需要在每个Promise对象中单独捕获异常了。至于Promise错误的“冒泡”性质是怎么实现的,就留给你课后思考了
通过这种方式,我们就消灭了嵌套调用和频繁的错误处理,这样使得我们写出来的代码更加优雅,更加符合人的线性思维。
Promise与微任务
function executor(resolve, reject) {
resolve(100)
}
let demo = new Promise(executor)
function onResolve(value){
console.log(value)
}
demo.then(onResolve)
这个执行顺序是啥? 首先执行newPromise时,Promise的构造函数会被执行,不过由于Promise是V8引擎提供的,所以暂时看不到Promise构造函数的细节。
接下来,Promise的构造函数会调用Promise的参数executor函数。然后在executor中执行了resolve,resolve函数也是在V8内部实现的,那么resolve函数到底做了什么呢?我们知道,执行resolve函数,会触发demo.then设置的回调函数onResolve,所以可以推测,resolve函数内部调用了通过demo.then设置的onResolve函数。
不过这里需要注意一下,由于Promise采用了回调函数延迟绑定技术,所以在执行resolve函数的时候,回调函数还没有绑定,那么只能推迟回调函数的执行
function Bromise(executor) {
var onResolve_ = null
var onReject_ = null
//模拟实现resolve和then,暂不支持rejcet
this.then = function (onResolve, onReject) {
onResolve_ = onResolve
};
function resolve(value) {
//setTimeout(()=>{
onResolve_(value)
// },0)
}
executor(resolve, null);
}
观察上面这段代码,我们实现了自己的构造函数、resolve、then方法。接下来我们使用Bromise来实现我们的业务代码,实现后的代码如下所示:
function executor(resolve, reject) {
resolve(100)
}
//将Promise改成我们自己的Bromsie
let demo = new Bromise(executor)
function onResolve(value){
console.log(value)
}
demo.then(onResolve)
/*
Uncaught TypeError: onResolve_ is not a function
at resolve (<anonymous>:10:13)
at executor (<anonymous>:17:5)
at new Bromise (<anonymous>:13:5)
at <anonymous>:19:12
之所以出现这个错误,是由于Bromise的延迟绑定导致的,在调用到onResolve_函数的时候,
Bromise.then还没有执行,所以执行上述代码的时候,当然会报“onResolve_isnotafunction“的错误
也正是因为此,我们要改造Bromise中的resolve方法,让resolve延迟调用onResolve_
function resolve(value) {
setTimeout(()=>{
onResolve_(value)
},0)
}
*/
/*
上面采用了定时器来推迟onResolve的执行,不过使用定时器的效率并不是太高,好在我们有微任务,所以
Promise又把这个定时器改造成了微任务了,这样既可以让onResolve_延时被调用,又提升了代码的执行
效率。这就是Promise中使用微任务的原由了。
queueMicrotask
*/
总结
1. Promise中为什么要引入微任务?
resolve的时候then 还没绑定用setTimeout时机不好容易堵塞。
2. Promise中是如何实现回调函数返回值穿透的?
3. Promise出错后,是怎么通过“冒泡”传递给最后那个捕获异常的函数?