RxJS中列表为空时如何使用defaultIfEmpty切换数据源?
RxJS 空数据源切换与subscribe执行时机问题
问题描述
需要将两个结构不同的列表转换为同一类型,仅当第一个列表为空时才使用第二个列表。尝试使用defaultIfEmpty方法但未生效,代码如下:
const first = []; // 可为 [{code: 1}, {code: 2}] const second = [{id: 1}, {id: 2}] of(first).pipe( map((value) => {number: value.code}) ).pipe( defaultIfEmpty(of(second).pipe(map((value) => {number: value.id}))) ).subscribe(doSomething);
期望输出:
[{number: 1}, {number: 2}]
存在两个疑问:
- 若给定数据源为空,如何切换到另一个数据源?
subscribe方法会在map完成后调用,还是会为map中的每个项调用?
问题解答
1. 切换空数据源的正确实现
你的代码问题在于:of(first)是把整个数组作为单个值发射,哪怕数组为空,它依然会发射这个空数组,因此defaultIfEmpty永远不会触发(defaultIfEmpty仅在源Observable没有发射任何值时生效)。
下面提供两种可行的实现方式:
方式一:用iif做数据源判断
通过iif根据第一个数组是否为空选择数据源,再统一转换格式:
const first = []; const second = [{id: 1}, {id: 2}]; iif( () => first.length > 0, of(first), of(second) ).pipe( // 若需要逐个发射数组元素,用switchMap展开;若直接返回整个转换后的数组,替换成下面的map switchMap(arr => arr), map(item => ({ number: item.code !== undefined ? item.code : item.id })) // 直接返回整个数组的写法: // map(arr => arr.map(item => ({ number: item.code ?? item.id }))) ).subscribe(doSomething);
方式二:用switchMap做条件分支
在管道内通过switchMap判断数组是否为空,切换数据源后再转换格式:
const first = []; const second = [{id: 1}, {id: 2}]; of(first).pipe( switchMap(arr => arr.length > 0 ? of(arr) : of(second)), switchMap(arr => arr), // 可选:展开数组元素逐个发射 map(item => ({ number: item.code ?? item.id })) ).subscribe(doSomething);
如果不需要逐个发射元素,仅需返回转换后的完整数组,可进一步简化:
const first = []; const second = [{id: 1}, {id: 2}]; of(first).pipe( map(arr => { return arr.length > 0 ? arr.map(item => ({ number: item.code })) : second.map(item => ({ number: item.id })); }) ).subscribe(doSomething);
2. subscribe的执行时机
subscribe的调用次数取决于Observable发射值的方式:
- 发射单个值(如整个数组):
map处理完这个单个值后,subscribe会被调用一次,回调参数是处理后的完整数组。 - 逐个发射元素(如用
from或switchMap(arr => arr)展开数组):map处理每个元素后,subscribe会为每个元素调用一次,回调参数是单个转换后的对象。
示例对比:
// 场景1:发射整个数组,subscribe调用一次 of([{code:1}, {code:2}]).pipe( map(arr => arr.map(item => ({number: item.code}))) ).subscribe(result => { console.log(result); // 输出:[{number:1}, {number:2}](仅一次) }); // 场景2:逐个发射元素,subscribe调用两次 from([{code:1}, {code:2}]).pipe( map(item => ({number: item.code})) ).subscribe(result => { console.log(result); // 先输出{number:1},再输出{number:2} });
内容的提问来源于stack exchange,提问作者Rafa Acioly
相关产品推荐
相关产品推荐

