Promise并发场景下Array.find()方法未生效问题排查
问题根因
- 匹配逻辑字段写串了:你在
savedProducts.find()里写的判断条件是t.id === e.id,这里t是存在缓存里的产品数据,e是当前处理的订单数据,产品ID和订单ID是两个完全不相关的字段,永远不可能相等,所以哪怕缓存里已经有对应产品,find也永远匹配不到,temp始终是undefined。 - 空数组调用遍历方法不会执行回调:你在find回调里写的console.log没输出,根本不是数组方法没触发,是第一批并发任务启动的时候,
savedProducts刚被初始化成空数组——所有JS数组的遍历方法(find/forEach/map等)在空数组上调用时,根本不会执行传入的回调函数,自然打不出日志。等第一批请求把产品数据push进数组之后,又因为前面写的判断条件字段错了,永远匹配不上,才会让你觉得数组方法完全没工作。 - 并发竞态导致缓存失效:就算你把字段改对,现在的逻辑也挡不住重复请求。PromisePool开了5个并发,如果几个订单刚好对应同一个产品,这几个任务会同时查到缓存为空,同时发一样的产品查询请求,缓存根本拦不住。
- 缓存生命周期不对:你把
savedProducts定义在call方法里面,每次调用call都会新建一个空数组,相当于每次请求都从零开始攒缓存,跨请求的缓存完全不生效。
修复方案
- 把find的判断条件改成
t.id === e.product_id,用订单上存的产品ID和缓存里的产品ID做匹配。 - 加一个Map存正在请求中的产品Promise,同一个产品ID如果已经在发请求了,就直接等这个请求返回,不要重复发,解决并发下的竞态问题。
- 如果需要跨多次接口调用复用产品缓存,就把缓存数组、存请求中Promise的Map定义成类的属性,不要每次进call方法都重置;如果只需要单次call请求内去重,定义在方法里也可以,但必须加请求中状态的Map。
修复后的参考代码:
class OrderService { // 类属性,跨call调用持久化缓存,不需要跨调用缓存可以挪到call方法内 private savedProducts: ProductFullRes[] = []; // 记录正在请求中的产品Promise,解决并发竞态 private pendingProductMap = new Map<number | string, Promise<ProductFullRes>>(); async call(p: UserOrdersParams): Promise<OrderWithCheckoutWithPagination> { try { const orders = await this.orderRepo.getOrders({page: p.page, perPage: p.perPage}); const {results} = await PromisePool .for(orders.orders) .withConcurrency(5) .process(async (e) => { // 先查已经落地的缓存 const cachedProduct = this.savedProducts.find(t => t.id === e.product_id); let productRequest: Promise<ProductFullRes>; if (cachedProduct) { productRequest = Promise.resolve(cachedProduct); } else { // 查有没有正在发的相同请求 const pendingRequest = this.pendingProductMap.get(e.product_id); if (pendingRequest) { productRequest = pendingRequest; } else { // 没有缓存也没有在途请求,发起新请求 productRequest = this.productRepo.getProductById(e.product_id); this.pendingProductMap.set(e.product_id, productRequest); // 请求完成后存入缓存,清掉在途记录 productRequest.then(product => { this.savedProducts.push(product); this.pendingProductMap.delete(e.product_id); }) } } const temp = await productRequest; const checkout = await this.orderRepo.getOrderCheckout(e.id); return { order: e, checkout: checkout, product: temp } as OrdersWithCheckout; }) return { orders: results, pagination: { meta: orders.meta, links: orders.links } } } catch (e) { return Promise.reject(new NetworkFailure((e as Exception).codeMessage, (e as Exception).code)); } } }
内容的提问来源于stack exchange,提问作者reza47
相关产品推荐
相关产品推荐

