TypeScript Generics: Create a Generic Error Handler Function - typescript-generics

I am having an error handler which gets two parameters, error which is of type unknown. It can be an object, a string, etc. and then return value which is of generic type. It can be an empty string, empty array or an empty object.
Here's how my function looks:
const genericErrorHandler = <T>(error: any, returnValue: T): T => {
console.log('Error = ', error);
return returnValue;
};
But I am not sure about calling this function. Here's how I expect it to behave:
genericErrorHandler({statusCode:404},"Not Found") should return "Not Found"
genericErrorHandler("Internal Server Error", {}) should return {}
genericErrorHandler({message:"Invalid TOken"},[]) should return []
with their respective logging. How do I call this function for above 3 cases and do I need to modify my actual function too?

Related

task.result?.data returning null in HTTP Cloud Function call

I'm calling a Cloud Function from my app with the following code:
private fun search(g: String, l: GeoPoint): Task<String> {
val data = hashMapOf(
"uid" to user.uid,
"first_name" to user.first_name,
"age" to user.age,
"gender" to user.gender,
"bio" to user.bio,
"img1" to user.img1,
"img2" to user.img2,
"latitude" to l.latitude.toString(),
"longitude" to l.longitude.toString(),
"g" to g,
"fcmToken" to fcmToken,
"pronoun" to if (user.gender == "male") "him" else "her"
)
log("data: $data") // successfully logs all values as non-null
return functions
.getHttpsCallable("searchNearby")
.call(data)
.continueWith { task ->
log("result: ${task.result?.data}")
// This continuation runs on either success or failure, but if the task
// has failed then result will throw an Exception which will be
// propagated down.
val result = task.result?.data as String
result
}
.addOnSuccessListener { result ->
log("search(): success $result")
}
.addOnFailureListener { exception ->
log("search(): exception $exception") // exception kotlin.TypeCastException: null cannot be cast to non-null type kotlin.String
log("search(): localMsg ${exception.localizedMessage}") // localMsg null cannot be cast to non-null type kotlin.String
log("search(): cause ${exception.cause}") // cause null
log("search(): msg ${exception.message}") // msg null cannot be cast to non-null type kotlin.String
}
}
Why is task.result?.data returning null if every item in data is non-null?
EDIT
.addOnFailureListener() now returns another weird error: HashMap cannot be cast to java.lang.String.
Here is my cloud function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
const geofirestore = require('geofirestore');
const { GeoCollectionReference, GeoFirestore, GeoQuery, GeoQuerySnapshot } = require('geofirestore');
const user = {};
exports.searchNearby = functions.https.onCall((data, context) => {
return admin.firestore().collection('blocks').add({
user1name: "1142",
user1bio: data.bio,
user1img1: data.img1,
user1img2: data.img2,
user1fcmToken: data.fcmToken,
user1pronoun: data.pronoun
}).then(result => {
console.log("Successfully created document");
return { result: "Successfully created document" };
}).catch(error => {
console.log("Error creating document " + error);
return { errorCreatingDocument: error };
});
});
The document is successfully created, and { result: "Successfully created document" } successfully gets sent back to the client, but it's still firing an exception java.util.HashMap cannot be cast to java.lang.String. Any idea what the problem is? It's also especially hard to debug because it doesn't tell you what like in my Cloud Functions the error is.
You are returning a JavaScript object to the client from the callable function:
return { result: "Successfully created document" };
The object contains a single property named result. Since your function returns an object, your client needs to assume the result from the callable is a Map. The Map will have a single entry with key also named result. This should be pretty easy to verify if you just log the value of result here:
val result = task.result?.data
Instead of casting this to a String, cast it to Map<String, *>, and fetch the entry named result out of it:
val data = task.result?.data as Map<String, *>
val result = data["result"]
Or, you can change your function to return a string instead of a JavaScript object:
return "Successfully created document";

How to type annotate "function wrappers" (function which returns a function with the same signature as it's argument)

Is there a way to properly tell flow that I'm returning a function with the same signature as the function I'm passed, but not exactly the same function ?
This is an example of a "once" wrapper which prevents a function from being called multiple times, it works but uses an any-cast internally to make flow give up, I'd like to get rid of that cast and have 100% coverage:
module.exports.once = /*::<F:Function>*/(f /*:F*/) /*:F*/ => {
let guard = false;
return ((function () {
if (guard) { return; }
guard = true;
return f.apply(null, arguments);
}/*:any*/) /*:F*/);
};
Okay, first things first.
Your return value can currently never match F without your casting through any because the signature of the function you're returning is not the same because it can return undefined where the original may not.
(comment syntax removed for readability)
module.exports.once = <F: Function>(f: F): F => {
let guard = false;
return ((function () { // this function returns the return value of F or void
if (guard) { return; } // returning void
guard = true;
return f.apply(null, arguments);
}: any): F);
};
But to start typing this, we're gonna need to break down that function generic a little bit.
First of all, let's not use Function as it's generally better if we don't:
However, if you need to opt-out of the type checker, and don’t want to go all the way to any, you can instead use Function. Function is unsafe and should be avoided.
Also, we're going to extract the types of the arguments and the return value so we can manipulate them independently and construct a return type. We'll call them Args and Return so they're easy to follow.
module.exports.once = <Args, Return, F: (...Array<Args>) => Return>(
f: F
) ((...Array<Args>) => Return | void) => { // note `Return | void`
let guard = false;
return function () {
if (guard) { return; }
guard = true;
return f.apply(null, arguments);
};
};
Now that we're taking into account that our new function might return void everything type checks fine. But of course, the return type of our once function will no longer match the type of the passed function.
type Func = (number) => string;
const func: Func = (n) => n.toString();
const onceFunc: Func = module.exports.once(func); // error!
// Cannot assign `module.exports.once(...)` to `onceFunc` because
// undefined [1] is incompatible with string [2] in the return value.
Makes sense, right?
So, let's discuss the signature of this function. We want our return value to have the same signature as the function we pass in. Currently it doesn't because we're adding void to the signature. Do we need to? Why are we returning undefined? How can we always return the same type from our onced function? Well, one option would be to store the return value from the single call to the function and always return the stored return value for subsequent calls. This would kind of make sense because the whole point is to allow multiple calls but not perform any of the functions effects. So this way we can avoid changing the interface of the function, so we really don't need to know whether or not the function has been called before.
module.exports.once = <Args, Return, F: (...Array<Args>) => Return>(
f: F
): ((...Array<Args>) => Return) => {
let guard = false;
let returnValue: Return;
return function () {
if (guard) { return returnValue; }
guard = true;
returnValue = f.apply(null, arguments);
return returnValue;
};
};
type Func = (number) => string;
const func: Func = (n) => n.toString();
const onceFunc: Func = module.exports.once2(func);
One good question to ask at this point would be, why do the types match even if we're not technically returning exactly F? The answer to that is because functions in flow are structurally typed. So if they have the same arguments and return value, their types match.

Firebase Function Get Single Value From Database

I want to get a single value from Firebase Database within Firebase function. However, the Promise never returns and the chained method never executes. Here is the method that fetches a value from the database
function getFcmToken(username){
return admin.database().ref('tokens/{username}/fcmToken').once('value').then(snap => {
if(snap.exists()){
const token = Object.keys(snap.val());
console.log("FCM Token", token);
return token;
}
return [];
});
}
The above method was supposed to return a token, but I am not sure it is, so the method below does not get executed.
function sendNotification(keyword, username){
return getFcmToken(username).then(token => {
if(token.length > 0){
//Notification Detail
let payload = {
data:{
keyword: keyword
}
};
return admin.messaging().sendToDevice(token, payload);
}
});
}
In the console log, all I see is Promise pending.
How can I update the code above to return a single value, it appears it is returning an array?
Thanks
Your database ref path is wrong. You might wanted to replace username in path, but single quoted won't do that.
Firebase is listening on tokens/{username}/fcmToken, which doesn't exists. Hence on value event will not be triggered and so downline callback will not be executed.
You can use Template literals for building dynamic strings.
Try ref path as
`tokens/${username}/fcmToken`
Code:
function getFcmToken(username){
return admin.database().ref(`tokens/${username}/fcmToken`).once(...)
}

Sinon stub.returns() not returning correct value

Sinon stub.returns() method does not return the correct response according the documentation. Any ideas why?
stubThis = sinon.stub().returns('123');
console.log(stubThis); // returns stub, but expect it to return 123
You need to invoke stubThis: console.log(stubThis());
When you call sinon.stub() you get back a function that returns undefined:
var stub = sinon.stub();
stub() // return undefined
When you invoke the .returns method on that function you specify its return value and get back the original function:
stub.returns('123') === stub; // true
stub() // now returns '123'
So in your code above stubThis is not supposed to be '123'. It is a function whose return value is '123'. You just need to invoke stubThis:
console.log(stubThis()); // logs '123'

React-redux project - chained dependent async calls not working with redux-promise middleware?

I'm new to using redux, and I'm trying to set up redux-promise as middleware. I have this case I can't seem to get to work (things work for me when I'm just trying to do one async call without chaining)
Say I have two API calls:
1) getItem(someId) -> {attr1: something, attr2: something, tagIds: [...]}
2) getTags() -> [{someTagObject1}, {someTagObject2}]
I need to call the first one, and get an item, then get all the tags, and then return an object that contains both the item and the tags relating to that item.
Right now, my action creator is like this:
export function fetchTagsForItem(id = null, params = new Map()) {
return {
type: FETCH_ITEM_INFO,
payload: getItem(...) // some axios call
.then(item => getTags() // gets all tags
.then(tags => toItemDetails(tags.data, item.data)))
}
}
I have a console.log in toItemDetails, and I can see that when the calls are completed, we eventually get into toItemDetails and result in the right information. However, it looks like we're getting to the reducer before the calls are completed, and I'm just getting an undefined payload from the reducer (and it doesn't try again). The reducer is just trying to return action.payload for this case.
I know the chained calls aren't great, but I'd at least like to see it working. Is this something that can be done with just redux-promise? If not, any examples of how to get this functioning would be greatly appreciated!
I filled in your missing code with placeholder functions and it worked for me - my payload ended up containing a promise which resolved to the return value of toItemDetails. So maybe it's something in the code you haven't included here.
function getItem(id) {
return Promise.resolve({
attr1: 'hello',
data: 'data inside item',
tagIds: [1, 3, 5]
});
}
function getTags(tagIds) {
return Promise.resolve({ data: 'abc' });
}
function toItemDetails(tagData, itemData) {
return { itemDetails: { tagData, itemData } };
}
function fetchTagsForItem(id = null) {
let itemFromAxios;
return {
type: 'FETCH_ITEM_INFO',
payload: getItem(id)
.then(item => {
itemFromAxios = item;
return getTags(item.tagIds);
})
.then(tags => toItemDetails(tags.data, itemFromAxios.data))
};
}
const action = fetchTagsForItem(1);
action.payload.then(result => {
console.log(`result: ${JSON.stringify(result)}`);
});
Output:
result: {"itemDetails":{"tagData":"abc","itemData":"data inside item"}}
In order to access item in the second step, you'll need to store it in a variable that is declared in the function scope of fetchTagsForItem, because the two .thens are essentially siblings: both can access the enclosing scope, but the second call to .then won't have access to vars declared in the first one.
Separation of concerns
The code that creates the action you send to Redux is also making multiple Axios calls and massaging the returned data. This makes it more complicated to read and understand, and will make it harder to do things like handle errors in your Axios calls. I suggest splitting things up. One option:
Put any code that calls Axios in its own function
Set payload to the return value of that function.
Move that function, and all other funcs that call Axios, into a separate file (or set of files). That file becomes your API client.
This would look something like:
// apiclient.js
const BASE_URL = 'https://yourapiserver.com/';
const makeUrl = (relativeUrl) => BASE_URL + relativeUrl;
function getItemById(id) {
return axios.get(makeUrl(GET_ITEM_URL) + id);
}
function fetchTagsForItemWithId(id) {
...
}
// Other client calls and helper funcs here
export default {
fetchTagsForItemWithId
};
Your actions file:
// items-actions.js
import ApiClient from './api-client';
function fetchItemTags(id) {
const itemInfoPromise = ApiClient.fetchTagsForItemWithId(id);
return {
type: 'FETCH_ITEM_INFO',
payload: itemInfoPromise
};
}

Resources