在之前的系列中,我吧sequence翻译成了序列,题目我就不翻译了,感觉翻译过来的有点失去了点啥。其他好多地方都是用stream(流)来比喻和形容。
在Rxjs编程中你已经学了一些关于使用最频繁的操作符了。讨论什么是sequence的操作符感觉有点抽象。为了帮助开发者更容易的理解操作符,我们使用marble diagrams(弹子图?翻译估计有问题)来标准可视化。他们很形象的代表了异步的数据流,并且你可以在其他的rxjs资料里面找到。
让我们看下range操作符,它返回一个发射特定范围内整数的Observable:如下图所示:
这个长箭头代表着这个Observable,x轴代表者时间,每一个圆圈代表着内部调用onNext()时Observable发射的值。在产生第三个值之后,range调用了上图中用垂直线表示的onCompleted函数。
让我们看下包含若干Observable的例子。merge操作符操作两个不同的Observable,并且返回合并值的新的Observable。
var a = Rx.Observable.interval(200).map(function(i) {
return 'A' + i;
});
var b = Rx.Observable.interval(100).map(function(i) {
return 'B' + i;
});
Rx.Observable.merge(a, b).subscribe(function(x) {
console.log(x);
});
结果如下:
》B0, A0, B1, B2, A1, B3, B4…
map是用的最多的序列转化操作符。它需要一个Observable和一个函数,并且把函数应用于源Observable的每一个值。它返回一个新的转化后值的Observable。
在上面两种情况,src都是不会改变的。
这个代码,和下面的代码,使用logValue定义:
var logValue = function(val){console.log(val)};
他可能是我们传递给map去做一些异步计算改变那个值的函数。在某些状况下,map可能不会如期盼的那样工作,对于这些状况,更好的办法是使用flatMap操作符。
filter
filter需要一个Observable和一个函数,并且它会使用这个函数去测试Observable中的每一个元素。它将会返回一个序列,这个序列中所的元素都是那个函数返回true的值。
reduce
var avg = Rx.Observable.range(0, 5)
.reduce(function(prev, cur) {
return {
sum: prev.sum + cur,
count: prev.count + 1
};
}, { sum: 0, count: 0 })
.map(function(o) {
return o.sum / o.count;
});
var subscription = avg.subscribe(function(x) {
console.log('Average is: ', x);
});
》Average is: 2
var avg = Rx.Observable.interval(1000)
.scan(function (prev, cur) {
return {
sum: prev.sum + cur,
count: prev.count + 1
};
}, { sum: 0, count: 0 })
.map(function(o) {
return o.sum / o.count;
});
var subscription = avg.subscribe( function (x) {
console.log(x);
});
使用如上方式,我们就可以聚合某个消耗时间长或者没有时间限制的序列了。在上面的代码中,我们每秒产生了一个增量的整数,并且用scan取代了先前的reduce。我们就以每秒为间隔取得了到目前为止所有的平均值。
- flatMap
- 如果你又一个Observable它的结果是许多嵌套的Observable将则怎么做?大部分的时间,你想到的就是统一这些嵌套的Observable的元素到一个单个的序列中。这也是flatMap所要明确干的事。
- flatMap操作符需要一个Observable参数,这个参数的元素也是Observable,并且返回只有一个孩子Observable的平坦值的Observable。
我们可以看到到每一在A(A1,A2,A3)中的元素也是Observable序列,一旦我们对A应用flatMap转化功能,我们将获得一个Observable,它包含所A所有不同孩子的所有元素。
function concatAll(source) {
return source.reduce(function(a, b) {
return a.concat(b);
});
}
我们可以这样使用它:
concatAll([[0, 1, 2], [3, 4, 5], [6, 7, 8]]);
// [0, 1, 2, 3, 4, 5, 6, 7, 8]
var counter = Rx.Observable.interval(1000);
var subscription1 = counter.subscribe(function(i) {
console.log('Subscription 1:', i);
});
var subscription2 = counter.subscribe(function(i) {
console.log('Subscription 2:', i);
});
setTimeout(function() {
console.log('Canceling subscription2!');
subscription2.dispose();
}, 2000);
结果将会如下:
》Subscription 1: 0
Subscription 2: 0
Subscription 1: 1
Subscription 2: 1
Canceling subscription2!
Subscription 1: 2
Subscription 1: 3
Subscription 1: 4
…
2)通过Operator的隐式取消
大部分的时候,操作符将会为你自动取消subscription。像range或者take,当序列结束或者满足某个操作的条件时,他们将会取消订阅。更高级的如withLastestFrom或者flatMapLastest等操作符,当它们动态创建Observable时,将会在需要订阅的时候内部自动地创建和销毁。
当我们使用Observable包装外部没有提供取消功能的API接口时,当取消Observable时,Observable将会停止发送通知,但是里面的API将不会被注销。例如你使用一个包装着promise的Observable,当取消时Observable会停止发射,但是里面的promise不会停止。
var p = new Promise(function(resolve, reject) {
window.setTimeout(resolve, 5000);
});
p.then(function() {
console.log('Potential side effect!');
});
var subscription = Rx.Observable.fromPromise(p).subscribe(function(msg) {
console.log('Observable resolved!');
});
subscription.dispose();
5秒之后我们将会看到:
》Potential side effect!
function getJSON(arr) {
return Rx.Observable.from(arr).map(function(str) {
var parsedJSON = JSON.parse(str);
return parsedJSON;
});
}
getJSON([ '{"1": 1, "2": 2}', '{"success: true}', // Invalid JSON string '{"enabled": true}' ]).subscribe(
function(json) {
console.log('Parsed JSON: ', json);
},
function(err) {
console.log(err.message);
}
);
结果如下:
》Parsed JSON: { 1: 1, 2: 2 }
JSON.parse: unterminated string at line 1 column 8 of the JSON data
function getJSON(arr) {
return Rx.Observable.from(arr).map(function(str) {
var parsedJSON = JSON.parse(str);
return parsedJSON;
});
}
var caught = getJSON(['{"1": 1, "2": 2}', '{"1: 1}']).catch(
Rx.Observable.return({
error: 'There was an error parsing JSON'
})
);
caught.subscribe(
function(json) {
console.log('Parsed JSON: ', json);
},
// Because we catch errors now, `onError` will not be executed
function(e) {
console.log('ERROR', e.message);
}
);
//This will try to retrieve the remote URL up to 5 times.
Rx.DOM.get('/products').retry(5)
.subscribe(
function(xhr) { console.log(xhr); },
function(err) { console.error('ERROR: ', err); }
);