Promise
引言:谈谈你对Promise的理解?
为什么会出现Promise?
promise的api有哪些?
手写Promise
Promise有哪些缺陷?
为什么和什么?
为什么出现:在promise出现之前,需要进行连续多个异步操作任务,每个任务都依赖于前一个操作任务完成后才能执行,这个时候是通过连续的回调函数嵌套完成的。这个时候代码就变得难以阅读和维护,这种情况为回调地狱问题。回调地狱问题例子:
// 多个回调函数嵌套,导致结构很不清晰。
// 假设有三个异步操作:获取用户数据、获取用户帖子数据、获取帖子评论数据
function getUser(userId, callback) {
setTimeout(() => {
console.log("Fetching user...");
const user = { id: userId, name: "John Doe" };
callback(null, user);
}, 1000);
}
function getPosts(userId, callback) {
setTimeout(() => {
console.log("Fetching posts...");
const posts = [{ id: 1, content: "Post 1" }, { id: 2, content: "Post 2" }];
callback(null, posts);
}, 1000);
}
function getComments(postId, callback) {
setTimeout(() => {
console.log("Fetching comments...");
const comments = [{ id: 1, content: "Comment 1" }, { id: 2, content: "Comment 2" }];
callback(null, comments);
}, 1000);
}
// 回调函数地狱示例:一层嵌套一层
getUser(1, (err, user) => {
if (err) {
console.error("Error fetching user:", err);
return;
}
getPosts(user.id, (err, posts) => {
if (err) {
console.error("Error fetching posts:", err);
return;
}
posts.forEach(post => {
getComments(post.id, (err, comments) => {
if (err) {
console.error("Error fetching comments:", err);
return;
}
console.log(`Post ${post.id} comments:`, comments);
});
});
});
});
什么是:Promise是异步编程的一种解决办法,用来获取异步操作结果值的对象。它的出现可以避免回调地狱问题,Promise的出现使得获取异步任务结果的方案变得更合理更强大。promise共有三种状态:Pending、Resolved、Rejected。当把异步任务放在Promise中处理,它的状态就是Pending,任务完成后并且成功变成Resolved,失败了就是Rejected。状态一旦改变了就不会再更改了!
// Promise解决回调地狱问题
function getUser(userId){
return new Promise((resolve, reject) => {
// 这里的异步操作用定时器代替
setTimeout(() => {
const user = {id : userId, name :"John"}
resolve(user)
},1000)
})
}
function getPosts(postId){
return new Promise((resolve, reject) => {
// 这里的异步操作用定时器代替
setTimeout(() => {
const posts = {id : postId, name :"John"}
resolve(posts)
},1000)
})
}
function getComments(postId){
return new Promise((resolve, reject) => {
// 这里的异步操作用定时器代替
setTimeout(() => {
const comments = {id : postId, name :"John"}
resolve(comments)
},1000)
})
}
// 这里再调用异步任务函数就不会出现上面的回调地狱情况
getUser(userId).then(user =>
getPosts(user.id)).then(posts => {
return Promise.all(posts.map(post => getComments(post.id)))
}).then(comments => {
comments.forEach((comment, index) => {
console.log(comment);
})
}).catch(err => {
console.log(err);
})
api有哪些?
Promise.resolve
Promise.reject
Promise.all
Promise.race
Promise.allSettled
Promise.any
promise.try
Promise.prototype.catch
Promise.prototype.finally
Promise.prototype.then
手写Promise
- Promise是一个构造函数,是异步编程的一种解决方案,通过Promise对象我们可以获取异步操作的消息。而
- Promise最主要的特性就是:链式调用、状态转换和错误处理。
手写实现的Promise及其then()和catch()方法的简化版
class MyPromise {
constructor(executor) {
this.state = 'pending'
this.value = undefined // 临时保存 resolve 中的参数
this.reason = undefined // 临时保存 reject 中的参数
this.onFulfilledCallbacks = [] // 装 then 中的回调
this.onRejectedCallbacks = [] // 装 catch 中的回调
const resolve = (value) => {
// 判断状态,保证 resolve,reject 只会执行一个状态
if (this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
// cb 代表数组里面的每一项
this.onFulfilledCallbacks.forEach(cb => cb(value))
}
}
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected'
this.reason = reason
// cb 代表数组里面的每一项
this.onRejectedCallbacks.forEach(cb => cb(reason))
}
}
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
// then方法并且要返回一个 Promise(),这样才能实现接多个 .then 的效果
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
return new MyPromise((resolve, reject) => {
const fulfilledCallback = () => {
try {
const result = onFulfilled(this.value);
resolve(result);
} catch (error) {
reject(error);
}
};
const rejectedCallback = () => {
try {
const result = onRejected(this.reason);
resolve(result);
} catch (error) {
reject(error);
}
};
if (this.state === 'fulfilled') {
setTimeout(fulfilledCallback, 0);
} else if (this.state === 'rejected') {
setTimeout(rejectedCallback, 0);
} else if (this.state === 'pending') {
this.onFulfilledCallbacks.push(fulfilledCallback);
this.onRejectedCallbacks.push(rejectedCallback);
}
});
}
// catch方法相当于then方法语法糖
catch(onRejected){
return this.then(null, onRejected)
}
}
// 使用示例
const p1 = new MyPromise((resolve, reject) => {
// 模拟异步操作
setTimeout(() => {
// resolve('成功');
reject('失败')
}, 1000);
})
p1.then(res => {
console.log(res); // 输出 "成功"
}).catch(err => {
console.log(err);
})
手写Promise.all
function myPromiseAll(promisesArr){
return new Promise((resolve, reject) => {
// 判断任务列队是否是数组不是的话 抛出错误
if(!Array.isArray(promisesArr)){
reject(new TypeError(`${promisesArr} must be an array`))
}
let promisesCount = 0
let results = []
promisesArr.forEach((p, index) => {
Promise.resolve(p).then(res => {
results[index] = res
promisesCount++
if(promisesCount === promisesArr.length){
resolve(results)
}
}, err => {
reject(err)
})
});
})
}
let a = new Promise((resolve) => resolve(1))
let b = new Promise((resolve) => resolve(2))
let c = new Promise((resolve,reject) => resolve(('成功')))
let d = new Promise((resolve) => resolve(3))
let e = new Promise((resolve) => {
setTimeout(() => {
resolve(4)
},4000)
})
myPromiseAll([a,b,c,d,e]).then(
res => {console.log(res); // [ 1, 2, '成功', 3, 4 ]
}).catch(err => {
console.log(err); // 出错了
}) // [1, 2, 3]
手写Promise.race
function myPromiseRace(promisesArr){
return new Promise((resolve, reject) => {
if(!Array.isArray(promisesArr)){
reject(new TypeError(`${promisesArr} is must an array!`))
}
promisesArr.forEach(p => {
// 对每个 Promise 进行处理
// 这里用 Promise.resolve()包装一下p是考虑promises中的值有可能不是promise对象
Promise.resolve(p).then(res => {
resolve(res)
}, err => {
reject(err)
})
})
})
}
/* myPromiseRace(a,b).then(res => {
console.log(res);
}).catch(err => {
console.log(err);
}) // [object Promise] is must an array! */
myPromiseRace([c,d]).then(res => {
console.log(res); //成功
}).catch(err => {
console.log(err);
})
Promise.all和Promise.allSettled的区别
Promise.all的广泛使用中,一旦出现reject的情况下,promise.all就停止了其他请求,这在某些情况下有不适合业务场景了,于是Promise的工具包里又多了一个方法Promise.allSettled
,是对Promise.all的一种补充,缓解了使用Promise.all碰到reject的痛点问题。
一句话概括Promise.allSettled和Promise.all的最大不同:Promise.allSettled永远不会被reject。
当需要处理多个Promise并行时,大多数情况下Promise.all用起来好使的,比如下面这样
const delay = (n) => new Promise((resolve) => setTimeout(resolve, n))
const promises = [delay(100).then(() => 1), delay(200).then(() => 2)]
Promise.all(promises).then((values) => console.log(values))
// 最终输出: [1, 2]
可是,是一旦有一个promise出现了异常,被reject了,情况就会变的麻烦。
const promises = [
delay(100).then(() => 1),
delay(200).then(() => 2),
Promise.reject(3),
]
Promise.all(promises).then((values) => console.log(values))
// 最终输出: Uncaught (in promise) 3
Promise.all(promises)
.then((values) => console.log(values))
.catch((err) => console.log(err))
// 加入catch语句后,最终输出:3
Promise.all能用catch捕获其中的异常,但其他执行成功的Promise的结果不会返回。要么全部成功,要么全部重来,这是Promise.all本身的强硬逻辑,也是痛点的来源,但这的确给Promise.allSettled留下了立足的空间。
const promises = [
delay(100).then(() => 1),
delay(200).then(() => 2),
Promise.reject(3),
]
Promise.allSettled(promises).then((values) => console.log(values))
// 最终输出:
// [
// {status: "fulfilled", value: 1},
// {status: "fulfilled", value: 2},
// {status: "rejected", value: 3},
// ]
当用Promise.allSettled时,我们只需专注在then语句里,当有promise被异常打断时,我们依然能妥善处理那些已经成功了的promise,不必全部重来。
手写Promise.allSettled
function myPromiseAllSettled(promisesArr){
return new Promise((resolve, reject) => {
if(!Array.isArray(promisesArr)){
reject(new TypeError(`${promisesArr} must be an array`))
}
let results = []
let cnt = 0
promisesArr.forEach((p,i) => {
Promise.resolve(p).then(res => {
results[i] = {status:"fulfilled", value: res}
cnt++
if(cnt === promisesArr.length){
resolve(results)
}
}, err => {
results[i] = {status: "rejected", reason: err}
cnt++
if(cnt === promisesArr.length){
resolve(results)
}
})
})
})
}
let a = new Promise((resolve) => resolve(1))
let b = new Promise((resolve) => resolve(2))
let c = new Promise((resolve,reject) => reject(('失败')))
let d = new Promise((resolve) => resolve(3))
let e = new Promise((resolve) => {
setTimeout(() => {
resolve(4)
},4000)
})
myPromiseAllSettled([a,b,c,d,e]).then(res => {
console.log(res);
}) // [ { status: 'fulfilled', value: 1 }, { status: 'fulfilled', value: 2 }, { status: 'rejected', reason: '失败' }, { status: 'fulfilled', value: 3 }, { status: 'fulfilled', value: 4 }]
手写Promise.any
- 作用:从多个
Promise
中返回第一个成功(resolve
)的Promise
结果或者等到所有状态变成rejected
状态,结果值才会变成rejected
状态。 - 只要参数实例有一个变成
fulfilled
状态,包装实例就会变成fulfilled
状态;如果所有参数实例都变成rejected
状态,包装实例就会变成rejected
状态。 - 使用场景:当你需要多个异步操作中的任意一个成功时使用,比如加载多个资源只需一个成功即可继续。
function myPromiseAny(promisesArr){
return new Promise((resolve, reject) => {
if(!Array.isArray(promisesArr)){
reject(new TypeError(`${promisesArr} must be an array`))
}
let result = []
let cnt = 0
promisesArr.forEach((p,i) => {
Promise.resolve(p).then((res) => {
resolve(res)
}, err => {
result[i] = err
cnt++
if(cnt === promisesArr.length){
reject(new AggregateError(result, 'All promises were rejeted'))
}
})
})
})
}
let a = new Promise((resolve) => resolve(1))
let b = new Promise((resolve) => resolve(2))
let c = new Promise((resolve,reject) => reject(('失败')))
let d = new Promise((resolve) => resolve(3))
let e = new Promise((resolve) => {
setTimeout(() => {
resolve(4)
},4000)
})
let f = new Promise((resolve,reject) => reject(('失败')))
myPromiseAny([a,b,c]).then(res => {
console.log(res); // 1
})
myPromiseAny([c,f]).then(res => {
console.log(res);
}).catch(err => {
console.log(err); // [errors]: [ '失败', '失败' ]
})
Promise有哪些缺陷?
- Promise虽然摆脱了回调地狱,但是then的链式调用也会带来额外的阅读负担。
- Promise传递中间值非常麻烦,Promise的错误捕获非常冗余。
- Promise的调试很差,由于没有代码块,你不能在⼀个返回表达式的箭头函数中设置断点,如果你在⼀个.then代码块中使用调试器的步进(step-over)功能,调试器并不会进入后续的.then代码块,因为调试器只能跟踪同步代码的每⼀步。
几个需要注意的点
异常穿透
在 Promise 的链式调用链中,当所有的 .then 中都没有指定错误处理的回调,则前面出现的异常会在最后失败的回调中处理。
new Promise((resolve, reject) => {
reject(1)
}).then(
value => {
console.log(value)
}).then(
value => {
console.log(value)
}).catch(reason => {
console.log('异常:' + reason)
})
注意:
-
.catch 所谓的异常穿透并不是一次失败状态就触发 catch,而是一层一层的传递下来的
-
异常穿透的前提条件是所有的 .then 都没有指定失败状态的回调函数。
-
如果 .catch 前的所有 .then 都指定了失败状态的回调函数,.catch 就失去了意义。
中断 Promise 链
- 什么是中断:当使用 promise 的 then 链式调用时,在中间中断,不再调用后面的回调函数
- 中断的方法:在回调函数中返回一个
pending
状态的 promise 对象
如何返回一个 pendding 状态的 promise 对象:
1. return new Promise(() => { }) // pending
2. return Promise.race([]) // pending Promise.race([]),race 方法接收一个空的可迭代对象时,该 Promise 会一直处于 pendding 状态。
// 中断之前:
Promise.resolve(1)
.then(value => {
console.log(value) // 1
}) // 下面都会打印
.then(value => console.log(value)) // undefined: 因为上面的then回调没有返回值
.then(value => console.log(value)) // undefined
// 中断之后:
Promise.resolve(1).then(value => {
console.log(value) // 1
// 返回一个 pending 状态的 promise 来中断这个调用链
return new Promise(() => { })
// 或者:return Promise.race([])
}) // 下面都不会打印
.then(value => console.log(value))
.then(value => console.log(value))
promise.then() 返回的新promise 的结果状态由什么决定?
简单表达:由 then() 指定的回调函数执行的结果决定
详细表达:
-
如果抛出异常,新 promise 变为 rejected, reason为抛出的异常
-
如果返回的是非 promise 的任意值,新promise变为 resolved,value 为返回的值
-
如果返回的是另一个新 promise,此 promise 的结果就会成为新 promise 的结果
改变 promise 状态和指定回调函数谁先谁后?
都有可能,正常情况下是先指定回调再改变状态,但也可以先改状态再指定回调
如何先改状态再指定回调?(在执行器中直接调用resolve()/reject())
-
在执行器中直接调用 resolve()/reject():立即
resolve()
的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。 -
延迟更长时间才调用 then()
什么时候才能得到数据?
-
如果先指定的回调,那当状态发生改变时,回调函数就会调用,得到数据
-
如果先改变的状态,那当指定回调时,回调函数就会调用,得到数据
// 正常情况,先指定回调再改变状态
const p2 = new Promise((resolve, reject) => {
// setTimeout 第三个参数,该参数将传入第一个函数参数
// 异步任务完成后,改变状态
setTimeout(resolve, 1000, '成功的数据')
// 后改变数据,同时传入值
}).then(
// 先指定回调函数,保存当前回调函数
value => {
console.log(value)})
// 先改状态,在指定回调函数
const p1 = new Promise((resolve, reject) => {
resolve(100)
// 先改变数据,同时传入值}).then(
// 后指定回调函数,异步执行回调函数
value => {
console.log(value)})
实现异步并发控制
题目:如果有100个请求甚至更多的请求,如何控制并发请求?(在同一时间,并发实现多个请求)
// 模拟请求列表
const requestList = [];
for(let i = 0; i < 100; i++){
requestList.push(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('done', i);
resolve(i)
}, Math.random() * 1000)
})
})
}
Promise.all
/* 方式一:全部并发:Promise all */
const parallelRun = async max => {
const requestSliceList = [];
for (let i = 0; i < requestList.length; i += max) {
requestSliceList.push(requestList.slice(i, i + max));
}
for (let i = 0; i < requestSliceList.length; i++) {
const group = requestSliceList[i];
try {
const res = await Promise.all(group.map(fn => fn()));
console.log('接口返回值为:', res);
} catch (error) {
console.error(error);
}
}
};
但是如果某个任务出错了,对于Promise.all 就会停止后续任务,直接返回错误任务的结果,显然是不对的。一组中一个请求失败就无法获取改组其他成员的返回值,这对于不需要判断返回值的情况倒是可以,但是实际业务中,返回值是一个很重要的数据, 我们可以接受某个接口失败了没有返回值,但是无法接受一个请求失败了,跟它同组的其他 9 个请求也没有返回值。
for(let i = 0; i < 100; i++){
requestList.push(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (i === 42) {
// 出错了,这次请求没有返回值
reject(new Error('出错了,出错请求:' + i));
} else {
console.log('done', i);
resolve(i);
}
}, Math.random() * 1000)
})
})
}
结果如下所示:可以看到当某一个任务出错后,只会返回错误任务的结果,停止后续的任务。
promise.all
Promise.allSettled()
先来看下权威的 MDN 的介绍
Promise.allSettled() 方法是 promise 并发方法之一。在你有多个不依赖于彼此成功完成的异步任务时,或者你总是想知道每个 promise 的结果时,使用 Promise.allSettled()
简单说就是:每个请求都会返回结果,不管失败还是成功
使用 Promise.allSettled()
替换下 Promise.all()
:
const parallelRun = async max => {
const requestSliceList = [];
for (let i = 0; i < requestList.length; i += max) {
requestSliceList.push(requestList.slice(i, i + max));
}
for (let i = 0; i < requestSliceList.length; i++) {
const group = requestSliceList[i];
try {
const res = await Promise.allSettled(group.map(fn => fn()));
console.log('接口返回值为:', res);
} catch (error) {
console.error(error);
}
}
};
结果如下所示:每个请求都会返回结果,不管失败还是成功。
promise.allSettled
接口全部正常有返回值,返回值中会正常记录当前请求时成功还是失败。
那如果有一个请求非常耗时,会出现什么情况?
答:有一个请求非常耗时,那组的请求返回就会很慢,会阻塞了后续的接口并发。
有没有什么方法可以解决这个问题?限制并发处理
可以维护一个运行池和一个等待队列,运行池始终保持 10 个请求并发,当运行池中有一个请求完成时,就从等待队列中拿出一个新请求放到运行池中运行,这样就可以保持运行池始终是满负荷运行,即使有一个慢接口,也不会阻塞后续的接口入池
最优解
实现一
利用Promise实现并发控制请求,设置并发的最大数量,当第一个任务完成,立马在放入新任务,知道所有任务完成!!!
代码:
/* 首页有10个并发请求, 先发送3个, 3个中哪一个响应了, 立即发送第4个, 直到第10个发送完成 */
function limitLoad(urls, handler, limit) {
const sequence = [].concat(urls)
let promise = []
// splice改变原数组的值
promise = sequence.splice(0, limit).map((url, index) => {
return handler(url).then(()=>{
return index
})
})
// p是第一个改变状态的promise对象
let p = Promise.race(promise)
// for循环给p赋值相当于.then().then()链式调用
for (let i = 0; i < sequence.length; i++) {
// p.then((res) => {console.log('this',res);});
p = p.then(res => {
// console.log(res, '这里的res是什么?--> 当前处理的任务在promise数组中对应的下标');
// 处理剩下的任务
promise[res] = handler(sequence[i]).then(()=>{
// console.log(res,'res是啥?---> 当前处理任务对应的下标值');
return res
})
return Promise.race(promise)
})
}
}
function loadImg(url){
return new Promise((reslove, reject)=>{
// 异步任务开始执行
console.log(url.info + '---start')
setTimeout(()=>{
//异步任务完成
console.log(url.info, 'ok!!!')
reslove()
}, url.time)
})
}
const urls =[
{info:'1', time:2000},
{info:'2', time:1000},
{info:'3', time:2000},
{info:'4', time:2000},
{info:'5', time:3000},
{info:'6', time:1000},
{info:'7', time:2000},
{info:'8', time:2000},
{info:'9', time:3000},
{info:'10', time:1000},
{info:'11', time:1000},
{info:'12', time:1000},
{info:'13', time:1000}
]
limitLoad(urls, loadImg, 4)
实现二
最优解:初始化一个运行池和任务队列,每当池子完成一个任务,从任务队列中拿出一个任务扔到池子里面,直到所有任务处理完毕 。
// 1.初始化运行池和任务等待队列
const pool = new Set()
const waitQueue = []
/**
* @description: 限制并发数量的请求
* @param {*} reqFn :请求方法
* @param {*} max :最大并发数
*/
const request = (reqFn, max) => {
return new Promise((resolve, reject) => {
// 判断运行池是否已满
const isFull = pool.size >= max
// 包装新的请求
const newReqFn = () => {
// reqFn是返回Promise对象的函数(模拟的异步任务)
reqFn().then(res => resolve(res))
.catch(err => {
reject(err)
}).finally(() => {
// 请求完成后,将该请求从运行池中删除
pool.delete(newReqFn)
const next = waitQueue.shift()
if(next){
pool.add(next)
next()
}
})
}
// 判断运行池是否已满,不满的话,运行池添加新任务,并执行新任务
if(isFull){
waitQueue.push(newReqFn)
}else{
pool.add(newReqFn)
newReqFn()
}
})
}
requestList.forEach(async item => {
const res = await request(item, 10)
console.log('res结果是:',res);
})
结果如下所示:可以看到每个任务都会正常执行,不会造成阻塞:
最优解
引入库 p-limit
安装:npm install p-limit -S
/* 方式四:使用库 */
import plimit from 'p-limit';
const limit = plimit(10);
requestList.forEach(async item => {
const res = await limit(item);
console.log(res);
});
async和await
async和await相当于是对generator函数的改进:
- 内置执行器
- 更好的语义
- 返回值是promise
async和await就是将generator函数转换成了Promise对象。async(相当于*)就是generator函数的语法糖,await(相当于yield)就是内部then命令的语法糖。async修饰的函数return的返回值,就是then回调函数的参数
async 和 Promise对象的区别
- async是一种语法,promise是一个内置对象。
- async函数本质上可以看做是多个异步任务操作包装成的Promise对象
- async函数在处理多个异步串行请求时更方便
- async 函数返回一个 Promise 对象,当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再执行函数体内后面的语句。可以理解为,是让出了线程,跳出了 async 函数体。
给定一个 URL 数组,如何实现接口的继发和并发
async 继发实现
// 继发一
async function loadData(){
const res1 = await fetch(url1)
const res2 = await fetch(url2)
const res3 = await fetch(url3)
return 'all done!'
}
// 继发二
async function loadData1(urls){
for(let url of urls){
const res = await fetch(url)
console.log(await res.text());
}
}
async 并发实现
// 并发一
async function loadData2(){
const res = await Promise.all([fetch(url1), fetch(url2), fetch(url3)])
return 'all done'
}
// 并发二
async function loadData3(url3){
// 并发读取url
const resArr = url3.map(async url => {
const res = await fetch(url)
return res
})
// 输出结果
for(const res of resArr){
console.log(await res);
}
}
try/catch 捕获多个错误并做不同的处理时,如何优化?
try catch 捕获错误,但是当我们需要捕获多个错误并做不同的处理时,很多 try catch
就会导致代码杂乱。为了简化这种错误的捕获,我们可以给 await 后的 promise 对象添加 catch 函数。
export default function handler(promise){
// 捕获Promise对象的结果值进行处理
return promise.then(data => {
return [null, data]
}).catch(err => [err])
}
async/await对比Promise的优势
- async/await代码读起来更加同步,Promise虽然摆脱了回调地狱,但是then的链式调用也会带来额外的阅读负担
- Promise传递中间值非常麻烦,而async/await几乎是同步的写法,非常优雅
- 错误处理友好,async/await可以用成熟的try/catch,Promise的错误捕获非常冗余
- 调试友好,Promise的调试很差,由于没有代码块,你不能在⼀个返回表达式的箭头函数中设置断点,如果你在⼀个.then代码块中使⽤调试器的步进(step-over)功能,调试器并不会进⼊后续的.then代码块,因为调试器只能跟踪同步代码的每⼀步。