When developing a library, should we throw errors/exceptions? - javascript

I'm developing a pub/sub Mediator library to be used by others and I don't know how to handle errors.
Here's an example piece of code:
/**
*
* #param String channel Channel to subscribe
* #param String|function callback Function name or callback
* #param String context Context to bind function to
* #param Boolean once True to subscribe once
**/
this.subscribe = function (channel, callback, context, once) {
if (!_.isObject(context)) {
context = window;
}
if (!_.isFunction(subscription) && !_.isFunction(context[subscription])) {
throw new Error('Function passed as callback to channel subscription named ' + channel + ' seems invalid!');
}
// Everything ok, add to channels object
channels[channel].push({fn: subscription, context: context || this, once: once});
}
This method is part of the library. It's API expects a channel name and a valid callback (either the function name, a lambda or a callback).
If the channel argument is not a string, the library readily stop. If an invalid context is passed, window is assumed. Mistakes in both these parameters are easily debugaable.
However, if an invalid callback is passed, it propagates the error until an event is fired (a channel publishing is made). In a pub/sub system this might become a nightmare to debug if, for instance, the event is rarely fired.
So my question is...
In this specific scenario, taking in account that I'm developing a javascript library to others, should I:
throw Errors to prevent further error propagation?
return false/undefined/-1 and not register the subscription?
proceed as normal at let it die somewhere, leaving the debugging to the third party developer
NOTE:
There's a similar question but my situation is a bit different and the answers provided didn't put my spirit at ease.

I guess this answer is more of my opinion and what I follow. You have to ask yourself if you should return throw an error and let the consumer, of your library, handle the error. If the error is something that cannot be recovered from, there is no need for you to throw an error (in JS). Your library should be able to fallback gracefully to alternatives but if it cannot do that for a certain function, it should return undefined. Most JS developers do check for undefined and it's a pretty common practice to do so while using library functions. Returning false is usually done in event handlers if an event cannot be handled.

Option #3 should be avoided. At least, return false;. You even might register the invalid callback, expecting that no events are published as well (since something went wrong). This "silent fail if not everything went wrong" might not really be applicable to your pub/sub example, yet there might be use cases for it.
Option #2 sounds good. It somehow makes the callback parameter explicitly optional, which could be a feature of your library - if the user is not sure himself whether he has a reason to subscribe, he can omit that test if you are doing it anyway. You might combine this with a console.warn() message in debugging versions.
Option #1 should be chosen if something really unexpected happened. It allows you to fail with a very descriptive custom Error message. This could be the case if the callback is a truthy object, but not callable, or the channel argument is a boolean or something - depending on how closed your interface design is / how much overloading you provide (in your case you could accept strings to be evaled as callbacks, or arrays of channel strings for example).

Errors and Exceptions are supposed exactly to stop the execution and notify the caller about it.
"throw Errors to prevent further error propagation?" is exactly write choice if your code is not supposed to be executed in that case.
Still, if you expect something to be passed wrong, you can return some special value and describe it in the documentation. Default values are widely used by developers, that's not intuitive,but mostly works tho:
/*
* ...
* Returns a Socket if found, otherwise undefined
*/
function FindSocketOrUndefined(): Socket|undefined {
...
return undefined
}

Related

Mixpanel returning a response object instead of undefined even when opted out in AVA Unit Test

I'm trying to implement an AVA Unit Test for my mixpanel implementation. To do this, I'm comparing the result of mixpanel.track() where if it returns anything, the track was successful, otherwise, it should be undefined.
I thought maybe it was that it was using a different mixpanel instance so I tried creating a named instance and ensuring that but it was to no avail. I'm also trying the same process but with Amplitude and it seems to be working fine (when I am opted out, the response fails as expected)
I have done this in my components where if
const test = mixpanel.track('event_name', {}) is successful, !!test === true but if I do mixpanel.opt_out.tracking() prior to const test = mixpanel.track('event_name', {}), then !!test === undefined.
Expected behaviour (and the observed behaviour when I use it in my components):
trackResponse === undefined
Observed behaviour:
trackResponse === { event: 'asdf',
properties:
{ '$browser': 'Safari',
'$current_url': 'about:blank',
'$browser_version': null,
'$screen_height': 0,
'$screen_width': 0,
mp_lib: 'web',
'$lib_version': '2.30.1',
time: 1572898982.142,
distinct_id: '[some_id]',
'$device_id': '[some_id]',
'$initial_referrer': '$direct',
'$initial_referring_domain': '$direct',
token: '[token]' } }
where [some_id] and [token] are some distinct values I've deleted.
I don't understand why in the AVA test, I'm receiving a response when normally a failed track() results in an undefined response. Could someone shine some light on this?
Let me know if I need to provide any additional information. Thanks.
I figured it out in case anyone else runs into this issue.
I used a debugger to step into the mixpanel.track() calls and figured out that to see if the user had opted out, mixpanel checks for a property in the localStorage and compares it to see if it's === to '0'. If this fails, it assumes the user has not opted out and carries out the track call as normal.
I guess during the AVA test, it was unable to access this property and assumed the user had not opted out. To fix it, in my call to mixpanel.init(), I added opt_out_tracking_persistence_type: 'cookie' as an option so that my opt_out call was being saved somewhere that the property could be accessed during the test.

How do I throw an error on a behaviour subject and continue the stream?

On one end, I have a stream which may occasionally throw an error:
this.behaviorSubject.error(error)
Later on, however, I want to continue the stream:
this.behaviorSubject.next(anotherValue)
on the other end, I have a subscriber subscribed to behaviorSubject.asObservable().
In the subscribtion, I'm handling the value and the error:
.subscribe(
( value ) => { /* ok */ },
( error ) => { /* some error */ }
);
I want the effect to be the same as a simple onSuccess and onError callback, where onError is called every time an error occurs and doesn't prevent future onSuccess calls from being made. How do I do this with RXJS?
I've looked into catch but it seems to just prevent error from being called on subscribers.
Short answer: It's not possible.
How to work with this: The basic concept of RxJS is that any error or complete-call will basically "kill" a stream. This concept forces you not "just to throw around errors here and there as you please" but to handle errors and the flow of data within your application properly. A BehaviorSubject for example is typically meant to hold data, however it should not be used to also include the process of retrieving/creating that data and handle possible errors that might occur during the retrieval of the data.
So if you want to go by the book, you should split up your flow into two parts:
Retrieval/creation of the data: A stream, that will run once then then completes and/or throws an error whenever one occurs.
When the data is retrieved it will be sent to the store.
The store (e.g. as in your case: a bunch of BehaviorSubjects): Only valid data arrives in the store, this means that no error-handling is done here and all parts relying on the store can trust in the store that it holds the correct data.
As an example your data flow could look as follows (as a rough sketch):
store.ts
dataStore: BehaviorSubject<IData> = new BehaviorSubject<IData>();
errorMessage: BehaviorSubject<IErrorMsg> = new BehaviorSubject<IErrorMsg>();
data-retrieval.ts
fetchDataById(id: string) {
httpService.get(`some/rest/endpoint/${id}`)
.subscribe(handleData, handleError);
}
handleData(data: IData) {
errorMessage.next(null);
dataStore.next(data);
}
handleError(error: Error) {
errorMessage.next(error.message);
dataStore.next(null);
}
"But this looks like a lot of overhead..." - True, however it ensures a clean and easy-to-understand flow of data within your application, that is easy to test and maintain. Also there are ready-to-use store-concepts like ngrx or redux that could be used.
Rx is fundamentally built upon the concept that an observable is either active or finalized (onComplete or onError). When an Observable is finalizing it will unSubscribe from its upstream Observable. No .catch can fix that behaviour, it only gives you the option to map the error to something else.
Rx.Observable.interval(500)
.mergeMap(i => i % 3 == 2 ? Rx.Observable.throw(new Error('kboom')) : Rx.Observable.of(i))
.catch(err => Rx.Observable.of(err.message))
.subscribe(
val => console.log('val: ' + val),
err => console.log('err: ' + err),
() => console.log('stream completed')
)
Note that this example completes after 3 emissions instead of 5
When you invoke this.behaviorSubject.error(error) it wil internally finalize the Observable contained in your Subject. If you want to somehow emit errors then you need to make your errors non-error values:
this.behaviorSubject.next({ value: 'somevalue' });
this.behaviorSubject.next({ error: error });
this.behaviorSubject.next({ value: 'somevalue' });
Then you are able to distinguish based on the properties on your emitted value what action you should take.
This may not work for your situation, but I ran into this same issue when work with Angular 2 because we would navigate across screens and would want the service to retry an API and not just call the error function again. It would actually cause bigger issues because the function was called in our constructor and the error function would try to update the UI which was not yet ready.
What I did seems to work fine and be pretty clean. I created a reset the Subject in the error handler.
subject.subscribe(
( value ) => { /* ok */ },
( error ) => {
//handle error
//reset subject
this.subject = new Subject<any>();
}
);
This works in our case because every time you navigate to the screen new subscriptions are getting torn down from the old screen then set up in the new, so the new subject won't hurt anything.
As others have said, the error result is expected to be terminal.
Personally I think (in your case) there are 3 types of results (technically 4)
The ideal result is the success case that calls next().
A lethal fail (out of memory error or any other form of "call can't continue" error) should call error().
The third form and the one that is key to your problem is the non-terminal error. It is the "Result was not a success" form. Because you mean to continue, it is not an Error in the rxjs sense. It is merely another type of result. A result that says something else happened.
(The 4th form is "processing completed": done all I can and am exiting without error)
Now I'm not sure of the details, but as I recall typescript can handle union types (if not you might have to play with a result type of "any").
With Unions you can declare your object as (for example) Subject<string|failtype>
The point here is you can send different results from the next statement.
You'd do something like the following...
DoCoolThingFunction():Subject<string|failtype>
{
const response = new Subject<string|failtype>();
deeperPeriodicAsyncOperation.subscribe((result) => {
if (result is something I like) {
response.next(result.toString());
} else if (result is final value) {
response.next(result.toString());
response.complete();
} else if (result is something teminal) {
response.error(result);
} else if (result is non-terminal error) {
response.next(new failtype(result));
}
});
return response;
}
Basically, this is saying "An error in this range is non-terminal. As such it is not an error, it is just a different kind of operational data".
Of course it is up to your receiving code to determine which type of result it has been handed. I've no idea if there are any neat ways to do that. It'd be great if the result handler could have multiple different typed responses ((result:string) =>{}, (result:failtype)=>{}) etc. but that's not really a topic for this thread.

how to work with react-native's PushNotificationIOS.getInitialNotification

I'm trying to detect whether my react-native app was launched by the user tapping a push-notification banner (see this excellent SO answer on the topic).
I've implemented the pattern Mark describes, and have discovered that the "notification" objects being provided by PushNotificationIOS.getInitialNotification are really bizarre, at least in cases when there isn't a notification to retrieve. Detecting this case has been a PITA, and I'm actually quite confused.
From what I can tell, PushNotificationIOS.getInitialNotification returns a promise; this promise is supposed to resolve with either null or an actual notification object -- null when there is no notification waiting for the user. This is the scenario I'm trying to detect and support.
Here's why it's such a pain to detect; the following tests were all run when there is no notification to find:
// tell me about the object
JSON.stringify(notification);
//=> {}
// what keys does it have?
Object.keys(notification);
//=> [ '_data', '_badgeCount', '_sound', '_alert' ]
So it stringifies to empty, but it has four keys? K...
// tell me about the data, then
JSON.stringify(notification._data);
//=> undefined
// wtf?
These bizarre facts frustrate both my understanding and my ability to distinguish between cases where there's an actual notification to react to vs. cases where the mailbox is empty. Based on these facts, I assumed I could test for the members I want, but even the most careful probing produces false positives 100% of the time:
PushNotificationIOS.getInitialNotification()
.then((notification) => {
// usually there is no notification; don't act in those scenarios
if(!notification || notification === null || !notification.hasOwnProperty('_data')) {
return;
}
// is a real notification; grab the data and act.
let payload = notification._data.appName; // TODO: use correct accessor method, probably note.data() -- which doesn't exist
Store.dispatch(Actions.receivePushNotification(payload, true /* true = app was awaked by note */))
});
Every time I run this code, it fails to trigger the escape hatch and throws on let payload because undefined is not an object (evaluating 'notification._data.appName').
Can someone explain what's going on here? Is PushNotificationIOS.getInitialNotification broken or deprecated? How in JS is it possible to have a key that evaluates to undefined? How can I detect this scenario?
Experienced javascripter, pretty puzzled here. Thanks for any help.
BTW: using react-native v0.29.0
The notification is an instance of PushNotification, not a plain object, that's why it stringifies to an empty object since no custom toString was implemented for it.
It sounds like a bug to me (which should be reported if not already) that the object is created when no notification is available.
Anyway, to workaround this issue, your check should actually be:
if(!notification || !notification.getData()) {
return;
}
Update: Issue has been fixed in 0.31 - see Github issue for more details.

Javascript structured error handling

I am developing my first Javascript app and I am trying to go object oriented.
There is a basic closure that returns my primary object and every function I invoke rests in that object. Some pseudo code would look like this:
primary = (function(){
var object = {
doSomething = function(){};
},
return {intance:function(return object)}
});
//invocation
primary.instance().doSomething();
What I am trying to achieve is to attach an error handler function to my object, so that whenever there is an internal error, it is cought, and I don't have to wrap every function call in a try catch block.
I tried object.onerrorbut the error went on to window object. Maybe I am getting the concept wrong. I tried searching on Github for some simpler framework that includes structured error handling, but no luck. I am pretty familiar with this in PHP, but I haven't done this so far in Javascript. Can somebody show me an example how it is done right?
EDIT: I know that structured error handling goes further, I am just trying to get a root handler, so that no errors / exceptions can pass on to the window object
Dealing with the error event without a try catch block will halt the execution of your script (except for any asynchronous functions that have already been called).
You can suppress (non-ajax, non-syntax) errors by capturing them on document.body or a more specific object, and stop them being thrown to the user (or reaching the window object) by using e.preventDefault() or return false, and send them to a global/object handler (to inspect or log) by passing the event object as an argument - but any of those options will stop your script execution beyond the point of error. That's the main benefit of a try catch block, and as far as I know there is no way around that.

Node.js + Mocha + Should.js How to test an error being thrown by an eventemitter?

I want to test the case where an error is thrown in my search.coffee class:
Let's assume that for my test, I cn fo
search.coffee
{ EventEmitter } = require 'events'
connectors = require './connectors'
class Search extends EventEmitter
constructor: (#query, #credentials, dispatch = true) ->
#connectors = #_connectors()
if #connectors.length == 0
# want to test this
#emit 'error', new Error 'Search could not be dispatched to any connectors'
#_dispatch(query, connector) for connector in #connectors if dispatch
I have tried the following, however since an event is being emitted with the error, the return of the new Search() itself does not throw an error. How can I catch this thrown error?
search_spec.coffee
Search = require '../../src/search'
describe "search.coffee", ->
describe "constructor", ->
it 'should return an error if no credentials match loaded connectors', ->
new Search("foo", { }, false).should.throw()
Yeah, nothing is thrown here. The default behavior if no event listener is installed is for Node to print a stack trace and exit. It looks like a thrown exception but you can't catch it. Systems like should.throw, assert.Throw, etc. rely on being able to catch the exception. Not possible here.
Now, there's a problem. You are emitting the 'error' event in your constructor. Who has the opportunity to call the on method to install a listener so that when this emit happens, someone will receive it? The object is not constructed yet. The object could call on on itself but no one outside can.
Options:
Don't emit an error, but instead throw an error. For the reason given above, this is the option I'd prefer if this were my code.
Add an additional parameter to the constructor. This would be a listener that Search installs on itself right away to listen for error events. So in testing, a listener could be passed to the constructor and then you just test that it is called.
If it does not make sense to allow callers to install a listener like mentioned in the 1st option, Search could install its own listener, which could record that that the object is dead (for instance, this.dead = true). Then test for this flag.
Or if the current design and behavior is the desired one (the constructor emits 'error', which causes Node.js to exit), modify the previous option. Devise a way to indicate to Search that it is running in a test environment. When it detects that it is in testing, it adds a listener like in the previous option.

Categories

Resources