Add Stream.lift (#1950)
* Add stream.lift and tests * Add docs * Add to change-log
This commit is contained in:
parent
a147023f4e
commit
76e585c523
4 changed files with 180 additions and 1 deletions
|
|
@ -45,6 +45,7 @@
|
||||||
- render/core: revamp the core diff engine, and introduce a longest-increasing-subsequence-based logic to minimize DOM operations when re-ordering keyed nodes.
|
- render/core: revamp the core diff engine, and introduce a longest-increasing-subsequence-based logic to minimize DOM operations when re-ordering keyed nodes.
|
||||||
- API: Introduction of `m.prop()` ([#2268](https://github.com/MithrilJS/mithril.js/pull/2268))
|
- API: Introduction of `m.prop()` ([#2268](https://github.com/MithrilJS/mithril.js/pull/2268))
|
||||||
- docs: Emphasize Closure Components for stateful components, use them for all stateful component examples.
|
- docs: Emphasize Closure Components for stateful components, use them for all stateful component examples.
|
||||||
|
- stream: Add `stream.lift` as a user-friendly alternative to `merge -> map` or `combine` [#1944](https://github.com/MithrilJS/mithril.js/issues/1944)
|
||||||
|
|
||||||
#### Bug fixes
|
#### Bug fixes
|
||||||
|
|
||||||
|
|
@ -123,6 +124,10 @@
|
||||||
|
|
||||||
- Fix IE bug where active element is null causing render function to throw error ([#1943](https://github.com/MithrilJS/mithril.js/pull/1943), [@JacksonJN](https://github.com/JacksonJN))
|
- Fix IE bug where active element is null causing render function to throw error ([#1943](https://github.com/MithrilJS/mithril.js/pull/1943), [@JacksonJN](https://github.com/JacksonJN))
|
||||||
|
|
||||||
|
#### Ospec improvements:
|
||||||
|
|
||||||
|
- Log using util.inspect to show object content instead of "[object Object]" ([#1661](https://github.com/MithrilJS/mithril.js/issues/1661), [@porsager](https://github.com/porsager))
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### v1.1.3
|
### v1.1.3
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
- [Stream.merge](#streammerge)
|
- [Stream.merge](#streammerge)
|
||||||
- [Stream.scan](#streamscan)
|
- [Stream.scan](#streamscan)
|
||||||
- [Stream.scanMerge](#streamscanmerge)
|
- [Stream.scanMerge](#streamscanmerge)
|
||||||
|
- [Stream.lift](#streamlift)
|
||||||
- [Stream.HALT](#streamhalt)
|
- [Stream.HALT](#streamhalt)
|
||||||
- [Stream["fantasy-land/of"]](#streamfantasy-landof)
|
- [Stream["fantasy-land/of"]](#streamfantasy-landof)
|
||||||
- [Instance members](#instance-members)
|
- [Instance members](#instance-members)
|
||||||
|
|
@ -151,6 +152,37 @@ Argument | Type | Required | De
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
##### Stream.lift
|
||||||
|
|
||||||
|
Creates a computed stream that reactively updates if any of its upstreams are updated. See [combining streams](#combining-streams). Unlike `combine`, the input streams are a variable number of arguments (instead of an array) and the callback receives the stream values instead of streams. There is no `changed` parameter. This is generally a more user-friendly function for applications than `combine`.
|
||||||
|
|
||||||
|
`stream = Stream.lift(lifter, stream1, stream2, ...)`
|
||||||
|
|
||||||
|
Argument | Type | Required | Description
|
||||||
|
------------ | --------------------------- | -------- | ---
|
||||||
|
`lifter` | `(any...) -> any` | Yes | See [lifter](#lifter) argument
|
||||||
|
`streams...` | list of `Streams` | Yes | Streams to be lifted
|
||||||
|
**returns** | `Stream` | | Returns a stream
|
||||||
|
|
||||||
|
[How to read signatures](signatures.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
###### lifter
|
||||||
|
|
||||||
|
Specifies how the value of a computed stream is generated. See [combining streams](#combining-streams)
|
||||||
|
|
||||||
|
`any = lifter(streams...)`
|
||||||
|
|
||||||
|
Argument | Type | Required | Description
|
||||||
|
------------ | -------------------- | -------- | ---
|
||||||
|
`streams...` | splat of `Streams` | No | Splat of zero or more streams that correspond to the streams passed to [`stream.lift`](#stream-lift)
|
||||||
|
**returns** | `any` | | Returns a computed value
|
||||||
|
|
||||||
|
[How to read signatures](signatures.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
##### Stream.HALT
|
##### Stream.HALT
|
||||||
|
|
||||||
A special value that can be returned to stream callbacks to halt execution of downstreams
|
A special value that can be returned to stream callbacks to halt execution of downstreams
|
||||||
|
|
@ -372,6 +404,18 @@ var greeting = stream.merge([a, b]).map(function(values) {
|
||||||
console.log(greeting()) // logs "hello world"
|
console.log(greeting()) // logs "hello world"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Or you can use the helper function `stream.lift()`
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var a = stream("hello")
|
||||||
|
var b = stream("world")
|
||||||
|
|
||||||
|
var greeting = stream.lift(function(_a, _b) {
|
||||||
|
return _a + " " + _b
|
||||||
|
}, a, b)
|
||||||
|
|
||||||
|
console.log(greeting()) // logs "hello world"
|
||||||
|
```
|
||||||
|
|
||||||
There's also a lower level method called `stream.combine()` that exposes the stream themselves in the reactive computations for more advanced use cases
|
There's also a lower level method called `stream.combine()` that exposes the stream themselves in the reactive computations for more advanced use cases
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ function finalize(stream) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function combine(fn, streams) {
|
function combine(fn, streams) {
|
||||||
if (!streams.every(valid)) throw new Error("Ensure that each item passed to stream.combine/stream.merge is a stream")
|
if (!streams.every(valid)) throw new Error("Ensure that each item passed to stream.combine/merge/lift is a stream")
|
||||||
return initDependency(createStream(), streams, function() {
|
return initDependency(createStream(), streams, function() {
|
||||||
return fn.apply(this, streams.concat([streams.filter(changed)]))
|
return fn.apply(this, streams.concat([streams.filter(changed)]))
|
||||||
})
|
})
|
||||||
|
|
@ -148,11 +148,20 @@ function scanMerge(tuples, seed) {
|
||||||
return newStream
|
return newStream
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function lift() {
|
||||||
|
var fn = arguments[0]
|
||||||
|
var streams = Array.prototype.slice.call(arguments, 1)
|
||||||
|
return merge(streams).map(function(streams) {
|
||||||
|
return fn.apply(undefined, streams)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
createStream["fantasy-land/of"] = createStream
|
createStream["fantasy-land/of"] = createStream
|
||||||
createStream.merge = merge
|
createStream.merge = merge
|
||||||
createStream.combine = combine
|
createStream.combine = combine
|
||||||
createStream.scan = scan
|
createStream.scan = scan
|
||||||
createStream.scanMerge = scanMerge
|
createStream.scanMerge = scanMerge
|
||||||
|
createStream.lift = lift
|
||||||
createStream.HALT = HALT
|
createStream.HALT = HALT
|
||||||
|
|
||||||
if (typeof module !== "undefined") module["exports"] = createStream
|
if (typeof module !== "undefined") module["exports"] = createStream
|
||||||
|
|
|
||||||
|
|
@ -200,6 +200,127 @@ o.spec("stream", function() {
|
||||||
o(spy.callCount).equals(0)
|
o(spy.callCount).equals(0)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
o.spec("lift", function() {
|
||||||
|
o("transforms value", function() {
|
||||||
|
var stream = Stream()
|
||||||
|
var doubled = Stream.lift(function(s) {return s * 2}, stream)
|
||||||
|
|
||||||
|
stream(2)
|
||||||
|
|
||||||
|
o(doubled()).equals(4)
|
||||||
|
})
|
||||||
|
o("transforms default value", function() {
|
||||||
|
var stream = Stream(2)
|
||||||
|
var doubled = Stream.lift(function(s) {return s * 2}, stream)
|
||||||
|
|
||||||
|
o(doubled()).equals(4)
|
||||||
|
})
|
||||||
|
o("transforms multiple values", function() {
|
||||||
|
var s1 = Stream()
|
||||||
|
var s2 = Stream()
|
||||||
|
var added = Stream.lift(function(s1, s2) {return s1 + s2}, s1, s2)
|
||||||
|
|
||||||
|
s1(2)
|
||||||
|
s2(3)
|
||||||
|
|
||||||
|
o(added()).equals(5)
|
||||||
|
})
|
||||||
|
o("transforms multiple default values", function() {
|
||||||
|
var s1 = Stream(2)
|
||||||
|
var s2 = Stream(3)
|
||||||
|
var added = Stream.lift(function(s1, s2) {return s1 + s2}, s1, s2)
|
||||||
|
|
||||||
|
o(added()).equals(5)
|
||||||
|
})
|
||||||
|
o("transforms mixed default and late-bound values", function() {
|
||||||
|
var s1 = Stream(2)
|
||||||
|
var s2 = Stream()
|
||||||
|
var added = Stream.lift(function(s1, s2) {return s1 + s2}, s1, s2)
|
||||||
|
|
||||||
|
s2(3)
|
||||||
|
|
||||||
|
o(added()).equals(5)
|
||||||
|
})
|
||||||
|
o("lifts atomically", function() {
|
||||||
|
var count = 0
|
||||||
|
var a = Stream()
|
||||||
|
var b = Stream.lift(function(a) {return a * 2}, a)
|
||||||
|
var c = Stream.lift(function(a) {return a * a}, a)
|
||||||
|
var d = Stream.lift(function(b, c) {
|
||||||
|
count++
|
||||||
|
return b + c
|
||||||
|
}, b, c)
|
||||||
|
|
||||||
|
a(3)
|
||||||
|
|
||||||
|
o(d()).equals(15)
|
||||||
|
o(count).equals(1)
|
||||||
|
})
|
||||||
|
o("lifts default value atomically", function() {
|
||||||
|
var count = 0
|
||||||
|
var a = Stream(3)
|
||||||
|
var b = Stream.lift(function(a) {return a * 2}, a)
|
||||||
|
var c = Stream.lift(function(a) {return a * a}, a)
|
||||||
|
var d = Stream.lift(function(b, c) {
|
||||||
|
count++
|
||||||
|
return b + c
|
||||||
|
}, b, c)
|
||||||
|
|
||||||
|
o(d()).equals(15)
|
||||||
|
o(count).equals(1)
|
||||||
|
})
|
||||||
|
o("lift can return undefined", function() {
|
||||||
|
var a = Stream(1)
|
||||||
|
var b = Stream.lift(function() {
|
||||||
|
return undefined
|
||||||
|
}, a)
|
||||||
|
|
||||||
|
o(b()).equals(undefined)
|
||||||
|
})
|
||||||
|
o("lift can return stream", function() {
|
||||||
|
var a = Stream(1)
|
||||||
|
var b = Stream.lift(function() {
|
||||||
|
return Stream(2)
|
||||||
|
}, a)
|
||||||
|
|
||||||
|
o(b()()).equals(2)
|
||||||
|
})
|
||||||
|
o("lift can return pending stream", function() {
|
||||||
|
var a = Stream(1)
|
||||||
|
var b = Stream.lift(function() {
|
||||||
|
return Stream()
|
||||||
|
}, a)
|
||||||
|
|
||||||
|
o(b()()).equals(undefined)
|
||||||
|
})
|
||||||
|
o("lift can halt", function() {
|
||||||
|
var count = 0
|
||||||
|
var a = Stream(1)
|
||||||
|
var b = Stream.lift(function() {
|
||||||
|
return Stream.HALT
|
||||||
|
}, a)["fantasy-land/map"](function() {
|
||||||
|
count++
|
||||||
|
return 1
|
||||||
|
})
|
||||||
|
|
||||||
|
o(b()).equals(undefined)
|
||||||
|
o(count).equals(0)
|
||||||
|
})
|
||||||
|
o("lift will throw with a helpful error if given non-stream values", function () {
|
||||||
|
var spy = o.spy()
|
||||||
|
var a = Stream(1)
|
||||||
|
var thrown = null;
|
||||||
|
try {
|
||||||
|
Stream.lift(spy, a, "")
|
||||||
|
} catch (e) {
|
||||||
|
thrown = e
|
||||||
|
}
|
||||||
|
|
||||||
|
o(thrown).notEquals(null)
|
||||||
|
o(thrown.constructor === TypeError).equals(false)
|
||||||
|
o(spy.callCount).equals(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
o.spec("merge", function() {
|
o.spec("merge", function() {
|
||||||
o("transforms an array of streams to an array of values", function() {
|
o("transforms an array of streams to an array of values", function() {
|
||||||
var all = Stream.merge([
|
var all = Stream.merge([
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue