Files
DefinitelyTyped/baconjs/baconjs-tests.ts
2015-07-20 07:33:53 +03:00

387 lines
14 KiB
TypeScript

/// <reference path="./baconjs.d.ts" />
function CreatingStreams() {
$("#my-div").asEventStream("click");
$("#my-div").asEventStream("click", ".more-specific-selector");
$("#my-div").asEventStream("click", (event, args) => args[0]);
$("#my-div").asEventStream("click", ".more-specific-selector", (event, args) => args[0]);
Bacon.fromPromise($.ajax("https://baconjs.github.io/"));
Bacon.fromPromise(Promise.resolve(1));
Bacon.fromPromise($.ajax("https://baconjs.github.io/"), true);
Bacon.fromPromise(Promise.resolve(1), false);
Bacon.fromPromise($.ajax("https://baconjs.github.io/"), true, (n:string) => {
return [new Bacon.Next(n), new Bacon.Next(() => n), new Bacon.End()];
});
Bacon.fromPromise(Promise.resolve(1), false, n => {
return [new Bacon.Next(n), new Bacon.Next(() => n), new Bacon.End()];
});
Bacon.fromEvent(document.body, "click").onValue(() => {
alert("Bacon!");
});
Bacon.fromEvent(document.body, "click", (event:MouseEvent) => event.clientX).onValue(clientX => {
alert("Bacon!");
});
Bacon.fromEvent(process.stdin, "readable", () => {
alert("Bacon!");
});
// This would create a stream that outputs a single value "Bacon!" and ends after that. The use of setTimeout causes the value to be delayed by 1 second.
Bacon.fromCallback(callback => {
setTimeout(() => {
callback("Bacon!");
}, 1000);
});
// You can also give any number of arguments to `fromCallback`, which will be passed to the function. These arguments can be simple variables, Bacon EventStreams or Properties. For example the following will output "Bacon rules":
Bacon.fromCallback((a, b, callback) => {
callback(a + " " + b);
}, Bacon.constant("bacon"), "rules").log();
{
var fs = require("fs"),
read = Bacon.fromNodeCallback(fs.readFile, "input.txt");
read.onError(error => {
console.log("Reading failed: " + error);
});
read.onValue(value => {
console.log("Read contents: " + value);
});
}
Bacon.once(new Bacon.Error("fail"));
// The following would lead to `1,2,3,1,2,3...` to be repeated indefinitely:
Bacon.fromArray([1, new Bacon.Error("")]);
Bacon.repeatedly(10, [1, 2, 3]);
// The following will produce values `0,1,2`.
Bacon.repeat(i => {
if (i < 3) {
return Bacon.once(i);
} else {
return false;
}
}).log();
{
var stream = Bacon.fromBinder(sink => {
sink("first value");
sink([new Bacon.Next("2nd"), new Bacon.Next("3rd")]);
sink(new Bacon.Next(() => {
return "This one will be evaluated lazily"
}));
sink(new Bacon.Error("oops, an error"));
sink(new Bacon.End());
return () => {
// unsub functionality here, this one's a no-op
};
});
stream.log();
}
new Bacon.Next("value");
new Bacon.Next(() => "value");
}
function CommonMethodsInEventStreamsAndProperties() {
// Converting strings to integers, skipping empty values:
Bacon.once("").flatMap(text => {
return text != "" ? parseInt(text) : Bacon.never();
});
Bacon.sequentially(1, [1, 2, 3]).scan(0, (a, b) => a + b);
Bacon.sequentially(1, [1, 2, 3]).diff(0, (a, b) => Math.abs(b - a));
// If you have a EventStream `s` with a value sequence `1,2,3,4,5`, the respective values in `s.slidingWindow(2)` would be `[],[1],[1,2],[2,3],[3,4],[4,5]`:
Bacon.fromArray([1, 2, 3, 4, 5]).slidingWindow(2);
// The values of `s.slidingWindow(2,2)`would be `[1,2],[2,3],[3,4],[4,5]`:
Bacon.fromArray([1, 2, 3, 4, 5]).slidingWindow(2, 2);
{
var x = Bacon.fromArray([1, 2]), y = Bacon.fromArray([3, 4]);
x.zip(y, (x, y) => x + y);
}
{
var stream = Bacon.fromArray([1, 2]);
stream.log("New event in myStream");
stream.log();
}
Bacon.fromArray([1, 2, 3]).withStateMachine(0, (sum, event) => {
if (event.hasValue()) {
// had to cast to `number` because event:Bacon.Next<number>|Bacon.Error<{}>
return [sum + <number>event.value(), []];
}
else if (event.isEnd()) {
return [undefined, [new Bacon.Next(sum), event]];
}
else {
return [sum, [event]];
}
});
{
var property = Bacon.fromArray([1, 2, 3]).toProperty(),
who = Bacon.fromArray(["A", "B", "C"]).toProperty();
property.decode({1: "mike", 2: who});
property.decode({1: {type: "mike"}, 2: {type: "other", whoThen: who}});
}
{
// This is handy for keeping track whether we are currently awaiting an AJAX response:
var ajaxRequest = <Bacon.Observable<Error, JQueryXHR>>{},
ajaxResponse = <Bacon.Observable<Error, JQueryXHR>>{},
showAjaxIndicator = ajaxRequest.awaiting(ajaxResponse);
}
Bacon.fromArray([1, 2, -3, 3]).withHandler(function (event) {
if (event.hasValue() && event.value() < 0) {
this.push(new Bacon.Error("Value below zero"));
return this.push(new Bacon.End());
} else {
return this.push(event);
}
});
{
var src = Bacon.once(1),
obs = src.map(x => -x);
console.log(obs.toString()); // > "Bacon.once(1).map(function)"
obs.withDescription(src, "times", -1);
console.log(obs.toString()); // > "Bacon.once(1).times(-1)"
}
{
// Calculator for grouped consecutive values until group is cancelled:
var events = [
{id: 1, type: "add", val: 3},
{id: 2, type: "add", val: -1},
{id: 1, type: "add", val: 2},
{id: 2, type: "cancel"},
{id: 3, type: "add", val: 2},
{id: 3, type: "cancel"},
{id: 1, type: "add", val: 1},
{id: 1, type: "add", val: 2},
{id: 1, type: "cancel"}
],
keyF = (event:{id:number}) => event.id,
limitF = (groupedStream:Bacon.EventStream<string, {id:number; type:string; val?:number}>) => {
var cancel = groupedStream.filter(x => x.type === "cancel").take(1),
adds = groupedStream.filter(x => x.type === "add");
return adds.takeUntil(cancel).map(x => x.val);
};
Bacon.sequentially(2, events)
.groupBy(keyF, limitF)
.flatMap(groupedStream => groupedStream.fold(0, (acc, x) => acc + x))
.onValue(sum => {
console.log(sum); // returns [-1, 2, 8] in an order
});
}
}
function EventStream() {
// This creates the stream which doesn't produce any events and never ends:
Bacon.interval(1e1, 0).last();
Bacon.fromArray([1, 2, 2, 1])
.skipDuplicates().log(); // > returns [1, 2, 1] in an order
// You might get two events containing [1,2,3,4] and [5,6,7] respectively, given that the flush occurs between numbers 4 and 5:
Bacon.fromArray([1, 2, 3, 4, 5, 6, 7]).bufferWithTime(0);
// Here's an equivalent to `stream.bufferWithTime(10)`:
{
var stream = Bacon.fromArray([1, 2, 3, 4, 5, 6, 7]);
stream.bufferWithTime(f => {
setTimeout(f, 10);
});
}
// You will get output events with values `[1, 2]`, `[3, 4]` and `[5]`.
Bacon.fromArray([1, 2, 3, 4, 5]).bufferWithCount(2);
}
function Property() {
// This creates the property which doesn't produce any events and never ends:
Bacon.interval(1e1, 0).toProperty().last();
{
var property = Bacon.fromArray([1, 2, 3, 4, 5]).toProperty();
// If you want to assign your Property to the "disabled" attribute of a JQuery object, you can do this:
property.assign($("#my-button"), "attr", "disabled");
// A simpler example would be to toggle the visibility of an element based on a Property:
property.assign($("#my-button"), "toggle");
}
Bacon.fromArray([1, 2, 2, 1]).toProperty()
.skipDuplicates().log(); // > returns [1, 2, 1] in an order
}
function CombiningMultipleStreamsAndProperties() {
{
var property = Bacon.constant(1),
stream = Bacon.once(2),
constant = 3;
Bacon.combineAsArray(property, stream, constant)
.log(); // > returns [1, 2, 3]
}
{
// To calculate the current sum of three numeric Properties, you can do:
var property = Bacon.constant(1),
stream = Bacon.once(2),
constant = 3;
// NOTE: had to explicitly specify the typing for `x:number, y:number, z:number`
Bacon.combineWith((x:number, y:number, z:number) => x + y + z, property, stream, constant);
}
{
// Assuming you've got streams or properties named `password`, `username`, `firstname` and `lastname`, you can do:
var password = Bacon.constant("easy"),
username = Bacon.constant("juha"),
firstname = Bacon.constant("juha"),
lastname = Bacon.constant("paananen"),
// NOTE: you should provide `combineTemplate` typing explicitly!
loginInfo = Bacon.combineTemplate<string, {
magicNumber:number; userid:string; passwd:string;
name:{first:string; last:string}
}>({
magicNumber: 3,
userid: username,
passwd: password,
name: {first: firstname, last: lastname}
}).onValue(loginInfo => {
// and your new `loginInfo` property will combine values from all these streams using that template, whenever any of the streams/properties get a new value. It would yield a value:
console.log("`loginInfo` expected", {
magicNumber: 3,
userid: "juha",
passwd: "easy",
name: {first: "juha", last: "paananen"}
});
console.log("`loginInfo` actual", loginInfo);
});
// Note that all Bacon.combine* methods produce a `Property` instead of an `EventStream`. If you need the result as an `EventStream` you might want to use `property.changes()`:
Bacon.combineWith((firstname, lastname) => `${firstname} ${lastname}`, firstname, lastname).changes();
}
{
var x = Bacon.fromArray([1, 2, 3]),
y = Bacon.fromArray([10, 20, 30]),
z = Bacon.fromArray([100, 200, 300]);
Bacon.zipAsArray(x, y, z)
.log(); // > returns values `[1, 10, 100]`, `[2, 20, 200]` and `[3, 30, 300]`
}
// The following example would log the number 3.
// NOTE: had to explicitly specify the typing for `a:number, b:number`
Bacon.onValues(Bacon.constant(1), Bacon.constant(2), (a:number, b:number) => {
console.log(a + b);
});
}
function $Event() {
new Bacon.Next("value");
new Bacon.Next(() => "value");
}
function Errors() {
// In case you want to convert (some) value events into Error events, you may use `flatMap` like this:
// NOTE: had to explicitly specify the typing for `flatMap`
Bacon.fromArray([1, 2, 3, 4]).flatMap<number>(x => {
return x > 2 ? new Bacon.Error("too big") : x;
});
// Conversely, if you want to convert some Error events into value events, you may use `flatMapError`:
Bacon.fromArray<string, number>([1, 2, 3, 4]).flatMapError<number>(error => {
var isNonCriticalError = (error:string) => Math.random() < .5,
handleNonCriticalError = (error:string) => 42;
return isNonCriticalError(error) ? handleNonCriticalError(error) : new Bacon.Error(error);
});
// Note also that Bacon.js combinators do not catch errors that are thrown. Especially `map` doesn't do so. If you want to map things and wrap caught errors into Error events, you can do the following:
Bacon.fromArray([1, 2, 3, 4]).flatMap(x => {
var dangerousFunction = (x:number) => {
throw new Error("dangerous function!");
};
try {
return dangerousFunction(x);
} catch (e) {
return new Bacon.Error(e);
}
});
Bacon.once("https://baconjs.github.io/").flatMap(url => {
// `ajaxCall` gives `Error`s on network or server `Error`s.
var ajaxCall = (url:string) => {
return Bacon.fromPromise<JQueryXHR, JQueryXHR>($.ajax(url));
};
return Bacon.retry({
source: () => ajaxCall(url),
retries: 5,
isRetryable: error => error.status !== 404,
delay: context => 100 // Just use the same delay always
});
});
}
function JoinPatterns() {
{
// Consider implementing a game with discrete time ticks. We want to handle key-events synchronized on tick-events, with at most one key event handled per tick. If there are no key events, we want to just process a tick:
var tick = Bacon.interval(1e2, 0),
keyEvent = Bacon.fromEvent(document.body, "click", _ => Date.now()),
handleTick = (_:number) => `timestamp: NONE`,
handleKeyEvent = (timestamp:number) => `timestamp: ${timestamp}`;
Bacon.when(
[tick, keyEvent], (_:number, timestamp:number) => handleKeyEvent(timestamp),
[tick], handleTick
);
// Order is important here. If the [tick] patterns had been written first, this would have been tried first, and preferred at each tick.
}
{
// Join patterns are indeed a generalization of `zip`, and `zip` is equivalent to a single-rule join pattern. The following `Observable`s have the same output:
var a = Bacon.once("a"),
b = Bacon.once("b"),
c = Bacon.once("c"),
f = (a:string, b:string, c:string) => `a = ${a}; b = ${b}; c = ${c}.`;
Bacon.zipWith(f, a, b, c);
Bacon.when([a, b, c], f);
}
{
// The inputs to `Bacon.update` are defined like this:
var initial = 0,
x = Bacon.interval(1e3, 1),
y = Bacon.interval(2e3, 1),
z = Bacon.interval(1.5e3, 1);
// NOTE: had to explicitly specify the typing for `previous:number`
Bacon.update(initial,
[x, y, z], (previous:number, x:number, y:number, z:number) => previous + x + y + z,
[x, y], (previous:number, x:number, y:number) => previous + x + y
);
// As input, each function above will get the previous value of the `result` Property, along with values from the listed Observables. The value returned by the function will be used as the next value of `result`. Just like in `Bacon.when`, only EventStreams will trigger an update, while Properties will be just sampled. So, if you list a single EventStream and several Properties, the value will be updated only when an event occurs in the EventStream.
}
{
// Here's a simple gaming example:
var scoreMultiplier = Bacon.constant(1),
hitUfo = new Bacon.Bus(),
hitMotherShip = new Bacon.Bus(),
score = Bacon.update(0,
[hitUfo, scoreMultiplier], (score:number, _:number, multiplier:number) => score + 100 * multiplier,
[hitMotherShip], (score:number, _:number) => score + 2000
);
// In the example, the `score` property is updated when either `hitUfo` or `hitMotherShip` occur. The `scoreMultiplier` Property is sampled to take multiplier into account when `hitUfo` occurs.
}
}