Rx.Observable
.from(1, 2, 3, 4, 5, 6, 7, 8)
.filter(function(val) { return val % 2; })
.map(function(val) { return val * 10; });
var evenTicks = 0;
function updateDistance(i) {
if (i % 2 === 0) {
evenTicks += 1;
}
return evenTicks;
}
var ticksObservable = Rx.Observable
.interval(1000)
.map(updateDistance)
ticksObservable.subscribe(function() {
console.log('Subscriber 1 - evenTicks: ' + evenTicks + ' so far');
});
如下是这个程序运行4秒之后的输出:
Subscriber 1 - evenTicks: 1 so far
Subscriber 1 - evenTicks: 1 so far
Subscriber 1 - evenTicks: 2 so far
Subscriber 1 - evenTicks: 2 so far
var evenTicks = 0;
function updateDistance(i) {
if (i % 2 === 0) {
evenTicks += 1;
}
return evenTicks;
}
var ticksObservable = Rx.Observable
.interval(1000)
.map(updateDistance)
ticksObservable.subscribe(function() {
console.log('Subscriber 1 - evenTicks: ' + evenTicks + ' so far');
});
ticksObservable.subscribe(function() {
console.log('Subscriber 2 - evenTicks: ' + evenTicks + ' so far');
});
四秒后输入如下:
Subscriber 1 - evenTicks: 1 so far
Subscriber 2 - evenTicks: 2 so far
Subscriber 1 - evenTicks: 2 so far
Subscriber 2 - evenTicks: 2 so far
Subscriber 1 - evenTicks: 3 so far
Subscriber 2 - evenTicks: 4 so far
Subscriber 1 - evenTicks: 4 so far
Subscriber 2 - evenTicks: 4 so far
function updateDistance(acc, i) {
if (i % 2 === 0) {
acc += 1;
}
return acc;
}
var ticksObservable = Rx.Observable
.interval(1000)
.scan(updateDistance, 0);
ticksObservable.subscribe(function(evenTicks) {
console.log('Subscriber 1 - evenTicks: ' + evenTicks + ' so far');
});
ticksObservable.subscribe(function(evenTicks) {
console.log('Subscriber 2 - evenTicks: ' + evenTicks + ' so far');
});
》
Subscriber 1 - evenTicks: 1 so far
Subscriber 2 - evenTicks: 1 so far
Subscriber 1 - evenTicks: 1 so far
Subscriber 2 - evenTicks: 1 so far
Subscriber 1 - evenTicks: 2 so far
Subscriber 2 - evenTicks: 2 so far
Subscriber 1 - evenTicks: 2 so far
Subscriber 2 - evenTicks: 2 so far
stringArray // represents an array of 1,000 strings
.map(function(str) {
❶ return str.toUpperCase();
})
❷ .filter(function(str) {
return /^[A-Z]+$/.test(str);
})
❸ .forEach(function(str) {
console.log(str);
});
背后有对应如下场景发生:
1:迭代数组,并创建所有项大写的新数组。
2:迭代这个大写的数组,并创建一个1000个元素的新数组。
3:迭代这个过滤后的数组,并且在控制台打印每一个结果。
在上面的数组的转化过程中,我们迭代了数组三次,并且创建了两个新的完整的大数组。这就是最大的不同。如果你是在关心变现或者是正在处理多item的sequence,你就不应该这样编码。
stringObservable // represents an observable emitting 1,000 strings
.map(function(str) {
❶ return str.toUpperCase();
})
❷ .filter(function(str) {
return /^[A-Z]+$/.test(str);
})
❸ .subscribe(function(str) {
console.log(str);
});
Observable pipeline和数组链极其相似,但是他们的相似也是仅仅止步于此,在一个Observable内部,不管我们申请了多少查询和修改,都不会发生任何操作直到我们订阅。当我们像map一样链一个改变,我们正组成了一个单个的函数在每个数组的项上仅仅操作了一次。所以在上面的代码中,将会发生如下:
1:创建一个转大写的函数,它将会应用在Observable的每一个项上,并且返回一个发射这些新项的Observable,当一个observer订阅它的时候。
2:和先前的转大写的函数一起组装一个新的过滤函数,并发射这些转大写、过滤后的新项的Observable,当仅当一个observer订阅它的时候。
3:这个Observable发射的触发器,应用我们给每元素定义的改变仅仅一次。
使用Observable,我们仅仅操作列表一次。我们仅仅应用这些操作当它真正需要的时候。例如,我们给上面的代码增加一个take操作符。
stringObservable
.map(function(str) {
return str.toUpperCase();
})
.filter(function(str) {
return /^[A-Z]+$/.test(str);
})
.take(5)
.subscribe(function(str) {
console.log(str);
});
var subject = new Rx.Subject();
var source = Rx.Observable.interval(300)
.map(function(v) { return 'Interval message #' + v; })
.take(5);
source.subscribe(subject);
var subscription = subject.subscribe(
function onNext(x) { console.log('onNext: ' + x); },
function onError(e) { console.log('onError: ' + e.message); },
function onCompleted() { console.log('onCompleted'); }
);
subject.onNext('Our message #1');
subject.onNext('Our message #2');
setTimeout(function() {
subject.onCompleted();
}, 1000);
》
onNext: Our message #1
onNext: Our message #2
onNext: Interval message #0
onNext: Interval message #1
onNext: Interval message #2
onCompleted
var delayedRange = Rx.Observable.range(0, 5).delay(1000);
var subject = new Rx.AsyncSubject();
delayedRange.subscribe(subject);
subject.subscribe(
function onNext(item) { console.log('Value:', item); },
function onError(err) { console.log('Error:', err); },
function onCompleted() { console.log('Completed.'); }
);
function getProducts(url) {
var subject;
❶ return Rx.Observable.create(function(observer) {
if (!subject) {
subject = new Rx.AsyncSubject();
❷ Rx.DOM.get(url).subscribe(subject);
}
❸ return subject.subscribe(observer);
});
}
❹ var products = getProducts('/products');
// Will trigger request and receive the response when read
❺ products.subscribe(
function onNext(result) { console.log('Result 1:', result.response); },
function onError(error) { console.log('ERROR', error); }
);
// Will receive the result immediately because it's cached
❻ setTimeout(function() {
products.subscribe(
function onNext(result) { console.log('Result 2:', result.response); },
function onError(error) { console.log('ERROR', error); }
);
}, 5000);
上面的代码中,当getProduct使用url别调用时,它返回一个observer(发射那个http get请求的结果)。下面就是它的拆解:
1:getProduct返回一个Observable sequence,我们在这里创建它。
2:如果我们没有创建一个AsyncSubject,我们创建一个把它订阅到Rx.Observalbe.get(url)返回的Observable上。
3:我们把observer订阅到AsyncSubject上,每次当一个observer订阅到那个Observable上,它都会实际上被订阅到那个AsyncSubject上,AsyncSubject扮演了获取url的Observable和Observer之间的代理。
4:我们创建了通过url“product.json”获取的那个Observable,并且把它在procucts变量中存储。
5:这是第一个订阅,它将开始url数据的获取,当结果获取完成后并打印出来。
6:这是第二个订阅,它将在第一个订阅完成5秒后开始。由于早前的url已经获取了,所以没有必要再次发起网络请求,它将会立即获取请求的结果,因为在AsyncSubject Subject中已近存在这个值。
这很有趣,当我们使用AsyncSubject订阅到一个Rx.DOM.Request.get Observable。由于AsyncSubject缓存最后一个结果,任何序列的订阅都会没有网络请求地立即接受到这个结果。可以使用AsyncSubject当我们期望一个单个结果并且保存它的时候。
var subject = new Rx.BehaviorSubject('Waiting for content');
subject.subscribe(
function(result) {
document.body.textContent = result.response || result;
},
function(err) {
document.body.textContent = 'There was an error retrieving content';
}
);
Rx.DOM.get('/remote/content').subscribe(subject);
var subject = new Rx.ReplaySubject(2); // Buffer size of 2
subject.onNext(1);
subject.onNext(2);
subject.onNext(3);
subject.subscribe(function(n) {
console.log('Received value:', n);
});
》
Received value: 2
Received value: 3
var subject = new Rx.ReplaySubject(null, 200); // Buffer size of 200ms
setTimeout(function() { subject.onNext(1); }, 100);
setTimeout(function() { subject.onNext(2); }, 200);
setTimeout(function() { subject.onNext(3); }, 300);
setTimeout(function() {
subject.subscribe(function(n) {
console.log('Received value:', n);
});
subject.onNext(4);
}, 350);
》
Received value: 2
Received value: 3
Received value: 4
- 在上面的例子中,我们创建了一个基于时间的buffer,而不是值的数量。我们的ReplaySubject缓存值,直到200毫秒前释放。我们发射了三个值每隔100毫秒,350毫秒之后,我们订阅到了一个observer并且发射了另外一个值。发生订阅的这个时候,它缓存了2和3,这是由于1发射在250秒之前,所以1不会被缓存了。
- Subject是一个能节省你好多时间的强有力的工具。它提供了好多好的解决途径如缓存和重复等。由于在它的核心,它既是Observable也是observer,所以你不需要学起其他的新东西。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Spaceship Reactive!</title>
<script src="../rx.all-4.0.0.js"></script>
<style> html, body { margin: 0; padding: 0; } </style>
</head>
<body>
<script src="spaceship.js"></script>
</body>
</html>
var canvas = document.createElement('canvas');
var ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var SPEED = 40;
var STAR_NUMBER = 250;
var StarStream = Rx.Observable.range(1, STAR_NUMBER)
.map(function() {
return {
x: parseInt(Math.random() * canvas.width),
y: parseInt(Math.random() * canvas.height),
size: Math.random() * 3 + 1
};
})
var SPEED = 40;
var STAR_NUMBER = 250;
var StarStream = Rx.Observable.range(1, STAR_NUMBER)
.map(function() {
return {
x: parseInt(Math.random() * canvas.width),
y: parseInt(Math.random() * canvas.height),
size: Math.random() * 3 + 1
};
})
.toArray()
.flatMap(function(starArray) {
return Rx.Observable.interval(SPEED).map(function() {
starArray.forEach(function(star) {
if (star.y >= canvas.height) {
star.y = 0; // Reset star to top of the screen
}
star.y += 3; // Move star
});
return starArray;
});
})
function paintStars(stars) {
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#ffffff';
stars.forEach(function(star) {
ctx.fillRect(star.x, star.y, star.size, star.size);
});
}
function paintStars(stars) {
ctx.fillStyle = '#000000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#ffffff';
stars.forEach(function(star) {
ctx.fillRect(star.x, star.y, star.size, star.size);
});
}
var SPEED = 40;
var STAR_NUMBER = 250;
var StarStream = Rx.Observable.range(1, STAR_NUMBER)
.map(function() {
return {
x: parseInt(Math.random() * canvas.width),
y: parseInt(Math.random() * canvas.height),
size: Math.random() * 3 + 1
};
})
.toArray()
.flatMap(function(starArray) {
return Rx.Observable.interval(SPEED).map(function() {
starArray.forEach(function(star) {
if (star.y >= canvas.height) {
star.y = 0; // Reset star to top of the screen
}
star.y += 3; // Move star
});
return starArray;
});
})
.subscribe(function(starArray) {
paintStars(starArray);
});
var HERO_Y = canvas.height - 30;
var mouseMove = Rx.Observable.fromEvent(canvas, 'mousemove');
var SpaceShip = mouseMove
.map(function(event) {
return {
x: event.clientX,
y: HERO_Y
};
})
.startWith({
x: canvas.width / 2,
y: HERO_Y
});
function drawTriangle(x, y, width, color, direction) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(x - width, y);
ctx.lineTo(x, direction === 'up' ? y - width : y + width);
ctx.lineTo(x + width, y);
ctx.lineTo(x - width,y);
ctx.fill();
}
function paintSpaceShip(x, y) {
drawTriangle(x, y, 20, '#ff0000', 'up');
}
function renderScene(actors) {
paintStars(actors.stars);
paintSpaceShip(actors.spaceship.x, actors.spaceship.y);
}
var Game = Rx.Observable
.combineLatest(
StarStream, SpaceShip,
function(stars, spaceship) {
return { stars: stars, spaceship: spaceship };
});
Game.subscribe(renderScene);
.subscribe(function(starArray) {
paintStars(starArray);
});
var ENEMY_FREQ = 1500;
var Enemies = Rx.Observable.interval(ENEMY_FREQ)
.scan(function(enemyArray) {
var enemy = {
x: parseInt(Math.random() * canvas.width),
y: -30,
};
enemyArray.push(enemy);
return enemyArray;
}, []);
var Game = Rx.Observable
.combineLatest(
StarStream, SpaceShip, Enemies,
function(stars, spaceship, enemies) {
return {
stars: stars,
spaceship: spaceship,
enemies: enemies
};
});
Game.subscribe(renderScene);
// Helper function to get a random integer
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function paintEnemies(enemies) {
enemies.forEach(function(enemy) {
enemy.y += 5;
enemy.x += getRandomInt(-15, 15);
drawTriangle(enemy.x, enemy.y, 20, '#00ff00', 'down');
});
}