-
Notifications
You must be signed in to change notification settings - Fork 145
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #673 from eccentric-j/subscribe-with-nil-file
Implement the Observable spec.
- Loading branch information
Showing
8 changed files
with
521 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
var isFunction = require('./isFunction'); | ||
|
||
/** | ||
* Coerce observer callbacks into observer object or return observer object | ||
* if already created. Will throw an error if both an object and callback args | ||
* are provided. | ||
* | ||
* @id createObserver | ||
* @param {function|object} onNext Function to receive new values or observer | ||
* @param {function} [onError] Optional callback to receive errors. | ||
* @param {function} [onComplete] Optional callback when stream completes | ||
* @return {object} Observer object with next, error, and complete methods | ||
* @private | ||
* | ||
* createObserver( | ||
* function (x) { console.log(x); }, | ||
* function (err) { console.error(err); }, | ||
* function () { console.log('done'); } | ||
* ) | ||
* | ||
* createObserver( | ||
* null, | ||
* null, | ||
* function () { console.log('done'); } | ||
* ) | ||
* | ||
* createObserver({ | ||
* next: function (x) { console.log(x); }, | ||
* error: function (err) { console.error(err); }, | ||
* complete: function () { console.log('done'); } | ||
* }) | ||
*/ | ||
function createObserver (onNext, onError, onComplete) { | ||
var isObserver = onNext && !isFunction(onNext) && typeof onNext === 'object'; | ||
|
||
// ensure if we have an observer that we don't also have callbacks. Users | ||
// must choose one. | ||
if (isObserver && (onError || onComplete)) { | ||
throw new Error('Subscribe requires either an observer object or optional callbacks.'); | ||
} | ||
|
||
// onNext is actually an observer | ||
if (isObserver) { | ||
return onNext; | ||
} | ||
|
||
// Otherwise create an observer object | ||
return { | ||
next: onNext, | ||
error: onError, | ||
complete: onComplete, | ||
}; | ||
} | ||
|
||
module.exports = createObserver; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
/** | ||
* Return a global context upon which to install Highland globals. Takes a | ||
* default namespace to use if both the node global and browser window | ||
* namespace cannot be found. | ||
* | ||
* @returns {object} Global namespace context | ||
*/ | ||
|
||
// Use the nodejs global namespace | ||
if (typeof global !== 'undefined') { | ||
module.exports = global; | ||
} | ||
// Use the browser window namespace | ||
else if (typeof window !== 'undefined') { | ||
module.exports = window; | ||
} | ||
// If neither the global namespace or browser namespace is avaiable | ||
// Use this module as the default context | ||
else { | ||
module.exports = this; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/** | ||
* Predicate function takes a value and returns true if it is a function. | ||
* | ||
* @id isFunction | ||
* @name isFunction(x) | ||
* @param {any} x - Any value to test against | ||
* @returns {bool} True if x is a function | ||
*/ | ||
|
||
function isFunction (x) { | ||
return typeof x === 'function'; | ||
} | ||
|
||
module.exports = isFunction; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
var _global = require('./global'); | ||
|
||
/* | ||
* Resolve nil value from global namespace if it exists. This may happen when | ||
* there are multiple versions of highland (like npm). | ||
* | ||
* nil is only equal to itself: | ||
* | ||
* nil === {} => false | ||
* nil === nil => true | ||
* | ||
* This property makes it valuable for determining a lack of input from a | ||
* falsey value such as nil or undefined. When a highland stream encounters | ||
* nil it knows for sure the intention is to end the stream. | ||
*/ | ||
|
||
if (!_global.nil) { | ||
_global.nil = {}; | ||
} | ||
|
||
module.exports = _global.nil; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
var nil = require('./nil'); | ||
|
||
/** | ||
* An implementation of the TC39 Subscription object | ||
* https://tc39.github.io/proposal-observable/#subscription-objects | ||
* | ||
* This class is intended for internal use only. | ||
* | ||
* Constructor takes a source highland stream, and an observer object with | ||
* an optional next, error, and complete methods. | ||
* | ||
* Returns a subscription object with a closed boolean and unsubscribe | ||
* method. | ||
* | ||
* @id ObservableSubscription | ||
* @name ObservableSubscription | ||
* @param {stream} stream - Highland stream to subscribe to | ||
* @param {object} observer - Observer to publish from stream subscription | ||
* @api private | ||
*/ | ||
function ObservableSubscription (stream, observer) { | ||
var self = this; | ||
|
||
// Set attributes | ||
this._source = stream.fork(); | ||
this.closed = false; | ||
|
||
// Don't let users subscribe to an already completed stream | ||
if (stream.ended) { | ||
if (observer.error) { | ||
observer.error(new Error('Subscribe called on an already completed stream.')); | ||
} | ||
|
||
this._cleanup(); | ||
|
||
return; | ||
} | ||
|
||
// Consume the stream and emit data to the observer | ||
this._source = this._source.consume(function (err, x, push, next) { | ||
if (err) { | ||
push(null, nil); | ||
if (observer.error) { | ||
observer.error(err); | ||
} | ||
self._cleanup(); | ||
} | ||
else if (x === nil) { | ||
if (observer.complete) { | ||
observer.complete(); | ||
} | ||
self._cleanup(); | ||
} | ||
else { | ||
if (observer.next) { | ||
observer.next(x); | ||
} | ||
next(); | ||
} | ||
}); | ||
|
||
this._source.resume(); | ||
} | ||
|
||
// Instance Methods | ||
|
||
/** | ||
* Perform cleanup routine on a subscription. This can only be called once per | ||
* subscription. Once its closed the subscription cannot be cleaned up again. | ||
* | ||
* Note: This relies heavily upon side-effects and mutates itself. | ||
* | ||
* @id ObservableSubscription.prototype._cleanup(subscription) | ||
* @name ObservableSubscription.prototype._cleanup | ||
* @returns {undefined} Side-effectful function cleans up subscription | ||
* @api private | ||
*/ | ||
|
||
ObservableSubscription.prototype._cleanup = function cleanup () { | ||
// Don't want to destroy\cleanup an already closed stream | ||
if (this.closed) { | ||
return; | ||
} | ||
this._source = null; | ||
this.closed = true; | ||
}; | ||
|
||
/** | ||
* Destroy the stream resources and cleanup the subscription. | ||
* @id ObservableSubscription.prototype.unsubscribe() | ||
* @name ObservableSubscription.prototype.unsubscribe() | ||
* @returns {undefined} Side-effectful. Destroys stream and cleans up subscription. | ||
* @api private | ||
*/ | ||
|
||
ObservableSubscription.prototype.unsubscribe = function unsubscribe () { | ||
// Don't want to destroy\cleanup an already closed stream | ||
if (this.closed) { | ||
return; | ||
} | ||
|
||
this._source.destroy(); | ||
this._cleanup(); | ||
}; | ||
|
||
module.exports = ObservableSubscription; |
Oops, something went wrong.