Aborting an ongoing Firebase database request - javascript

I was wondering if there is any way to cancel/abort a Firebase Realtime Database request when it hasn't returned any results yet.
Consider the following:
1) User selects an item from a list
2) (Web) app makes a request to retrieve some messages or other data for that item
3) The user navigates back and selects a different item
4) The Firebase request returns with results for the wrong item
A regular xhr request can be aborted. A timeout (setTimeout) can be cleared. Is there any way to abort/cancel an ongoing firebase request?
Example:
var request;
// ..
if (request) { abort(request); request = null; }
request = firebase.database().ref("item1/").once("value", callback);
PS: this question is about the database but I guess this would be really handy for most firebase services?

There is no way to abort a read operation.
If you don't want you app to use the data anymore, you can detach the listener by calling off().
This doesn't just work for listeners you register using .on() but also works for .once().
Example:
firebase.db.ref("/item1").once("value", function(snap) { console.log("1: ", snap.val()); });
firebase.db.ref("/item1").off();
firebase.db.ref("/item2").once("value", function(snap) { console.log("2: ", snap.val()); });
Prints the following in the console:
2: { ... }
In other words: the callback from the first line never gets called (unless it returns a result before .off() is called)

Related

How to add checking in axios request interceptors

I am using axios.interceptors and useContext to handle the request loading spin component in my React.js project.
When the request start, I set the context value - showLoading true, and change to false if it finished or error. The function works perfectly. Here is my sample code:
// Request
axios.interceptors.request.use(request => {
setGlobalLoading(true)
return request;
}, error => {
setGlobalLoading(false);
return Promise.reject(error);
});
// Response
axios.interceptors.response.use(response => {
setGlobalLoading(false);
return response;
}, error => {
setGlobalLoading(false);
return Promise.reject(error);
});
However, I have a page which is a table, and it need to call multiple api on each row, and all the api return so fast. Then it make the loading component "blinking".
So I have an idea, can I make "Only show the loading if the request take more than 1 second " or I should set the delay to close the loading? (But I don't know how to get any pending requests, because this may close the loading when another request not finish)
The callback function only call once, should I use the context or state to save the setTimeout and clearTimeout functions? or any better solution to handle this case?
you can create a array to save requset.
Usually request has a request id,you can use this request id.
if you send a request ,you can push request to array,in finally you can remove this request .
if this array.length >0 loading is show .else loading is close

How to make callable google cloud function idempotent

I have a google cloud function that sends notifications to a firebase topic.
The function was working fine till suddenly, it start to send more than one notification 2 or 3 at the same time. After contacting the Firebase support team, they told may I should make the function Idempotent, but I don't know how, since it's a callable function.
for more details, this is a reference question containing more detail about the case.
below is the function's code.
UPDATE 2
it was a bug in the admin sdk and they resolved it in the last release.
UPDATE
the function is already idempotent because it is an event driven function
the link above contains the functions log as prof it runs only once.
after 2 month on go and back it appears the problem with firebase admin sdk
the function code getMessaging().sendToTopic() has retry 4 times and the origin request so its 5 times by default before throwing error and terminate the function. So the reason of duplicate notification is that the admin sdk from time to time cant reach the FCM server for some reason.it try to send notification to all subs but in half way or before send all notification it get error so it retry again from the beginning so some users receives one notification and some get 2, 3,4.
And Now the question is how to prevent these default retries or how to make the retry continue from where it get the error. probably Ill ask a separated question.
For now I did a naive solution by prevent the duplicate notification from the receiver( mobile client). if it get more than one notification has same content within a minute show only one.
const functions = require("firebase-functions");
// The Firebase Admin SDK to access Firestore.
const admin = require("firebase-admin");
const {getMessaging} = require("firebase-admin/messaging");
const serviceAccount = require("./serviceAccountKey.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://mylinktodatabase.firebaseio.com",
});
exports.callNotification = functions.https.onCall( (data) => {
// Grab the text parameter.
const indicator = data.indicator;
const mTitle = data.title;
const mBody = data.body;
// topic to send to
const topic = "mytopic";
const options = {
"priority": "high",
"timeToLive": 3600,
};
let message;
if (indicator != null ) {
message = {
data: {
ind: indicator,
},
};
} else {
message = {
data: {
title: mTitle,
body: mBody,
},
};
}
// Send a message to devices subscribed to the provided topic.
return getMessaging().sendToTopic(topic, message, options)
.then(() => {
if (indicator != null ) {
console.log("Successfully sent message");
return {
result: "Successfully sent message", status: 200};
} else {
console.log("Successfully sent custom");
return {
result: "Successfully sent custom", status: 200};
}
})
.catch((error) => {
if (indicator != null ) {
console.log("Error sending message:", error);
return {result: `Error sending message: ${error}`, status: 500};
} else {
console.log("Error sending custom:", error);
return {result: `Error sending custom: ${error}`, status: 500};
}
});
});
In this blog Cloud Functions pro tips: Building idempotent functions, shows how to do a function idempotent using two approaches:
Use your event IDs
One way to fix this is to use the event ID, a number that uniquely identifies an event that triggers a background function, and— this is important—remains unchanged across function retries for the same event.
To use an event ID to solve the duplicates problem, the first thing is to extract it from the event context that is accessed through function parameters. Then, we utilize the event ID as a document ID and write the document contents to Cloud Firestore. This way, a retried function execution doesn’t create a new document, just overrides the existing one with the same content. Similarly, some external APIs (e.g., Stripe) accept an idempotency key to prevent data or work duplication. If you depend on such an API, simply provide the event ID as your idempotency key.
A new lease on retries
While this approach eliminates the vast majority of duplicated calls on function retries, there’s a small chance that two retried executions running in parallel could execute the critical section more than once. To all but eliminate this problem, you can use a lease mechanism, which lets you exclusively execute the non-idempotent section of the function for a specific amount of time. In this example, the first execution attempt gets the lease, but the second attempt is rejected because the lease is still held by the first attempt. Finally, a third attempt after the first one fails re-takes the lease and successfully processes the event.
To apply this approach to your code, simply run a Cloud Firestore transaction before you send your email, checking to see if the event has been handled, but also storing the time until which the current execution attempt has exclusive rights to sending the email. Other concurrent execution attempts will be rejected until the lease expires, eliminating all duplicates for all intents and purposes.
Also, as stated in this other question:
Q: Is there a need to make these onCall functions idempotent or will they never perform retries?
A: Calls to onCall functions are not automatically retried. It's up to your application's client-side and server-side code, to agree on a retry strategy.
See also:
Retrying Event-Driven Functions - Best practices

Unit test for a sub-method which called from main method and include http-request authoraztion

I faced to a really complicated scenario, hope you guys give me a hint.
So I have a main method, which is a api endpoint, this method call another method to check if the user is authorized to use this endpoint or not.
The sub-endpoint which I called it apiAuthorazation send a get request to a thirdparty url, and this third-party return a response which says this user is authorized, or not!
So I already have a unit test for the main method, but now I want add this authorization part to it. I know I can use muck libs like Nock or other similar libraries, but my problem is how can I add this sub-method to my uit test.
This is my api endpoint method :
module.exports.api = (event, context, callback) => {
// Authorization
let getBearertoken = event.headers.Authorization.replace("Bearer ", '');
let isAuhtorized = utilities.apiAuthorazation(getBearertoken);
//Some other Codes
}
As you can see I passed a bearer token to my sub-method, and apiAuthorazation method will going to send this token to a third-party api, and the method is like this :
module.exports.apiAuthorazation = function (token){
let url = process.env.authApiUrl
requestLib(`${url}/${token}`, function (error, response, body) {
if (error) console.log('Error while checking token :', error);
if(response.isValidUser){
return true;
}
else{
return false;
}
});
}
Now my question is how can I include this sub-method to my main method unit test. I use mocha and chai for unit testing, bceause the berear token will expire soon, so when I run the test, I send a sample event which have the berear token in it, but it's already expired, so its kind of useless.
When you unit test Api, you can mock apiAuthorization for the two scenarios (true or false) and test if Api behaves as expected. Dont worry about what happens inside the sub method at all for the Api tests as you are testing Api here and the focus is not on what is happening inside the sub method, apiAuthorization.

node.js and hapi: fetching data from a database synchronously

Coming from a .net world where synchronicity is a given I can query my data from a back end source such as a database, lucene, or even another API, I'm having a trouble finding a good sample of this for node.js where async is the norm.
The issue I'm having is that a client is making an API call to my hapi server, and from there I need to take in the parameters and form an Elasticsearch query to call, using the request library, and then wait for the instance to return before populating my view and sending it back to the client, problem being is that the request library uses a callback once the data is returned, and the empty view has long been returned to the client by then.
Attempting to place the return within the call back doesn't work since the EOF for the javascript was already hit and null returned in it's place, what is the best way to retrieve data within a service call?
EX:
var request = require('request');
var options = {
url: 'localhost:9200',
path: {params},
body: {
{params}
}
}
request.get(options, function(error, response){
// do data manipulation and set view data
}
// generate the view and return the view to be sent back to client
Wrap request call in your hapi handler by nesting callbacks so that the async tasks execute in the correct logic order. Pseudo hapi handler code is as following
function (request, reply) {
Elasticsearch.query((err, results) => {
if (err) {
return reply('Error occurred getting info from Elasticsearch')
}
//data is available for view
});
}
As I said earlier in your last question, use hapi's pre handlers to help you do async tasks before replying to your client. See docs here for more info. Also use wreck instead of request it is more robust and simpler to use

How much of `$.post` is blocking?

I have a form collecting some information that I use $.post to handle an ajax request.
$.post(ajaxEndpoint, dataObject)
.done(function (response) {
if (response.status === 'success') {
// Send data to process asynchronously
otherApiCall(response.otherData);
// Redirect to the thank you page
window.location.replace(getThankYouUrl());
}
});
function otherApiCall (data) {
$.post(otherAjaxEndpoint, data);
}
The problem I have, from what I'm guessing, is that it redirects too quickly before the other POST can be made. But I do want it to POST asynchronously then redirect so the user isn't waiting for that second response. I don't care what the result of the second response is. I just want to finish the first response, send a second POST and the redirect immediately to cut down on the user looking at a spinner.
My second $.post seems like it doesn't get sent in time before the redirect happens because I never get the data from it. If I comment out the redirect, I do. I don't want to wait until the second done() but I can't figure how not to. What am I not understanding and/or doing wrong?
Additional Information/Update
I do have control over the server side handling. Is there something on that end that I could do to get a response quickly without waiting for the rest of the processing to finish?
You probably want to let the second post complete and then do the redirect.
A simple fix would be to return the $.post from second method and use done() of the second call to manage the redirect
$.post(ajaxEndpoint, dataObject)
.done(function (response) {
if (response.status === 'success') {
// Send data to process asynchronously
otherApiCall(response.otherData).done(function(){
// second post call now complete
// Redirect to the thank you page
window.location.replace(getThankYouUrl());
}).fail(function(){
// handle failed response
});
}
});
function otherApiCall (data) {
return $.post(otherAjaxEndpoint, data);
}
The best way to send data back to a server without having to wait for it to complete would be to use the navigator.sendBeacon API.
navigator.sendBeacon('/url/to/handler', yourData);
Quote from MDN:
Using the sendBeacon() method, the data will be transmitted asynchronously to the web server when the User Agent has had an opportunity to do so, without delaying the unload or affecting the performance of the next navigation.
Your data will have to be made into a ArrayBufferView, Blob, DOMString, or FormData, and I'm not sure if it is technically a POST request or not, but the request will persist after redirection.
It is currently supported in Firefox 31+, Chrome 39.0+, Opera 26+. For other browsers, you would have to do something else. You can feature-detect like so.
if (navigator.sendBeacon) {
// Use sendBeacon API.
}
else {
// Something else.
}
The redirect is probably cancelling the AJAX request that has been queued, but not yet sent. Try doing the redirect after a timeout, to give the second AJAX call a chance to be sent.
$.post(ajaxEndpoint, dataObject)
.done(function(response) {
if (response.status === 'success') {
// Send data to process asynchronously
otherApiCall(response.otherData);
// Redirect to the thank you page
setTimeout(function() {
window.location.replace(getThankYouUrl());
}, 10);
}
});
I'm not sure how reliable this is, though. Perhaps a better solution would be to perform the redirect when the second AJAX call goes to readystate == 3, which means the server is processing the request. There's no jQuery interface to this, so you'll probably have to do it using the low-level XMLHttpRequest interface.

Categories