I'm trying to have a Google Cloud Function that invokes an external API. I'm on the Blaze plan, so I should be able to make external invocations. I have an Express app and these test routes:
app.get('/helloWorld', (request, response) => {
response.send('Hello there');
});
app.get('/test', (request, response) => {
request.get("https://postman-echo.com/get?foo1=bar1&foo2=bar2", (error, res, body) => {
console.log('error:', error);
console.log('statusCode:', res && res.statusCode);
console.log('body:', body);
if(error) {
response.status(400).send(error);
}
response.status(200).send(body);
});
});
The /helloWorld route works fine, but the /test route times out every time. If I look at the Firebase logs for the function I see:
9:19:29.837 PM
api
Function execution started
9:20:29.839 PM
api
Function execution took 60002 ms, finished with status: 'timeout'
9:21:09.263 PM
api
Function execution started
9:21:09.277 PM
api
Function execution took 14 ms, finished with status code: 200
9:21:13.515 PM
api
Function execution started
9:22:13.516 PM
api
Function execution took 60002 ms, finished with status: 'timeout'
So, it's like it just keeps calling the function over and over in an infinite loop and times out each time, and nothing gets returned to the client until it finally just times out. What am I doing wrong here?
Since you're calling a 3rd party asynchronous API, you have to tell Cloud Functions when your code is done. You do this by returning a promise from the function, and then making sure that promise resolves when all (asynchronous) work is done.
app.get('/test', (request, response) => {
return new Promise((resolve, reject) {
request.get("https://postman-echo.com/get?foo1=bar1&foo2=bar2", (error, res, body) => {
console.log('error:', error);
console.log('statusCode:', res && res.statusCode);
console.log('body:', body);
if(error) {
response.status(400).send(error);
reject(error);
}
response.status(200).send(body);
resolve();
});
});
});
You might want to consider using a library like request-promise to prevent the need for your own Promise logic.
Related
I have written a handler function inside my nextjs page/api folder;
handler(req, res) {}
Am using #influxdata/influxDb-client as mentioned in the documentation. Am using
from(queryAPI.rows(query).pipe(....).subscribe(next(value)=> {results.push(value}, complete(console.log(results); res.status(200).json(results)}
Am getting all the query value, once the observable is completed. it works most of the time.
Am pushing the intermediate results in the next part of the subscriber and trying to send the results back to client in the complete part of the subscriber. I want the request handler to wait till i get all my values from influx DB query in the complete part of the subscriber and can send the value back to client..
But the issue "Handler function will not Wait till the observable is completed". Handler function returns, before the observer gets completed. Am getting error: API resolved without sending a response...
I get all the values only when the observer is completed.
I don't know how to handle the scenario.
How can I make the handler function wait until the observable is completed?
I found the solution for the same
I used new Promise() with await, added my observable inside this promise and resolved the promise on Complete of the subscribe.
Code will look like the following :
export async function handler (req, res) {
const results=[];
await new Promise((resolve, reject) => {
from((queryAPIs.rows(query))
.pipe(map(({values, tableMeta}) => tableMeta.toObject(values)))
.subscribe(
{
next(object) => {results.push(object)}
complete() => { resolve (results) }
error(err) => { reject (err) }
});
res.status(200).send(results);
}
}
I've made firebase cloud function which adds the claim to a user that he or she has paid (set paid to true for user):
const admin = require("firebase-admin");
exports.addPaidClaim = functions.https.onCall(async (data, context) => {
// add custom claim (paid)
return admin.auth().setCustomUserClaims(data.uid, {
paid: true,
}).then(() => {
return {
message: `Succes! ${data.email} has paid for the course`,
};
}).catch((err) => {
return err;
});
});
However, when I'm running this function: I'm receiving the following error: "Unhandled Rejection (RangeError): Maximum call stack size exceeded". I really don't understand why this is happening. Does somebody see what could cause what's getting recalled which in turn causes the function to never end?
Asynchronous operations need to return a promise as stated in the documentation. Therefore, Cloud Functions is trying to serialize the data contained by promise returned by transaction, then send it in JSON format to the client. I believe your setCustomClaims does not send any object to consider it as an answer to the promise to finish the process so it keeps in a waiting loop that throws the Range Error.
To avoid this error I can think of two different options:
Add a paid parameter to be able to send a JSON response (and remove the setCustomUserClaim if it there isn’t any need to change the user access control because they are not designed to store additional data) .
Insert a promise that resolves and sends any needed information to the client. Something like:
return new Promise(function(resolve, reject) {
request({
url: URL,
method: "POST",
json: true,
body: queryJSON //A json variable I've built previously
}, function (error, response, body) {
if (error) {
reject(error);
}
else {
resolve(body)
}
});
});
My published Cloudflare Worker (wrangler publish --env environment_name) is timing out for clients, but not when
running locally (e.g. by using cfworker, a nice tool to emulate cloudflare workers locally)
or running in preview (wrangler preview --env environment_name).
A summary of my worker:
addEventListener('fetch', async (event) => {
const fetchEvent = event as FetchEvent
const results = await doSomeWork() // returns a promise
return fetchEvent.respondWith(new Response(JSON.stringify(results)))
})
My wrangler tail (production logs) output does complete (after I placed console.log statements in doSomeWork. There were no errors, and I got {"outcome":"ok"... in wrangler tail. I would have expected to get error code 1102 (Worker exceeded CPU time limit.) if time-out was happening.
It turns out that addEventListener cannot be passed an async function (or one that returns a promise). The fetchEvent.respondWith does accept a promise however. This is not written in the documentation, but I discovered this in lib.webworker.d.ts.
To do asynchronous work, you must return a promise to fetchEvent.respondWith instead:
So your alternatives are to:
Pass a promise to respondWith
addEventListener('fetch' (event) => {
const fetchEvent = event as FetchEvent
const responsePromise = doSomeWork().then((results) => new Response(JSON.stringify(results))
return fetchEvent.respondWith(responsePromise))
})
Or pass the result of an async function to respondWith (still a promise, I told you, you must return a promise)
addEventListener('fetch' (event) => {
const fetchEvent = event as FetchEvent
const responsePromise =
return fetchEvent.respondWith(async () => {
// I would put this async function in a different file (handlers.ts/.js), and name it `doSomeWorkHandler` to make it more readable though
const results = await doSomeWork()
return new Response(JSON.stringify({hello: "world"}))
}))
})
Why no timeout error?
The reason the timeout error doesn't happen is because even though Cloudflare Workers limits your CPU execution time to 10ms on the free plan, it doesn't stop your worker because you're not using the CPU in this bug/ edge case. It's doing nothing.
I've just upgraded to using Firebase Cloud Functions v1.x. According to this answer
Callable functions are exactly the same as HTTP functions
With that in mind, I've tried to convert my pre-1.x mock-code:
export const myHttpAction = functions.https.onRequest((req, res) => {
try {
const result = await myHttpActionWorker(req.body);
return res.send({ status: 'OK' });
} catch (err) {
console.error(err);
return res.status(500).send({ status: 'Server error' });
}
});
to the following:
export const myHttpAction = functions.https.onCall(async (data, context) => {
console.log(context.auth);
try {
const result = await myHttpActionWorker(data);
return { status: 'OK' };
} catch (err) {
console.error(err);
return { status: 'Server error' };
}
});
But upon submission to my endpoint, /myHttpAction, with the same data that I used in pre-1.x, I get the following back:
{
"error": {
"status": "INVALID_ARGUMENT",
"message": "Bad Request"
}
}
I'm not sure why the request is "bad" since it's exactly the same and Callable functions are "exactly the same". Any idea what gives?
My package.json specifies "firebase-functions": "^1.0.1".
You're misunderstanding what was meant by "exactly the same" (and omitting the entire remainder of the answer!). They're the same in terms of security (as the original question was asking), because a callable function is an HTTP function, with extra stuff going on behind the scenes that managed by the callable client SDK. The answer lists out those differences. Those differences don't have any effect on security. But you can't simply swap in a callable for an HTTP function and expect everything to be the same for existing callers.
If you want to invoke a callable function without using the client SDK, you'll have to follow its protocol specification. The documentation on that is forthcoming, but you can get the basics here:
How to call Firebase Callable Functions with HTTP?
In a React Native project, I wrote this function using Promise to do a job asynchronously;
function doEncryptionAsync(params) {
return new Promise(
function (resolve, reject) {
// Async code started
console.log('Promise started (Async code started)');
// The job that takes some times to process
var encrypted_value = new EncryptedValue(params);
if (true) {
resolveencrypted_value
}
else {
reject("Error while encrypting!");
}
}
)
}
And I call that in my Redux action;
export const encrypt = ( params ) => {
return (dispatch) => {
dispatch({
type: type.ENCRYPT
});
// Sync code started
console.log('Started (Sync code started)');
doEncryptionAsync(params)
.then((response) => {
// Async code terminated
console.log('Promise fulfilled (Async code terminated)');
encryptSuccess(dispatch, response);
})
.catch((error) => {
console.log(error);
encryptFail(dispatch);
});
// Sync code terminated
console.log('Promise made (Sync code terminated)');
}
}
It works, but not asynchronously! My main thread seems to be blocked until doEncryptionAsync() returns. The line console.log('Promise made (Sync code terminated)') runs, but not immediately!
My output for logs is like this;
// OUTPUT Simulation
Started (Sync code started) at time x
Promise started (Async code started) at time x
Promise made (Sync code terminated) at time (x + 2sec)
Promise fulfilled (Async code terminated) at time (x + 2sec)
My question is what's wrong with my approach to implement a AsyncTask?!
JavaScript's asynchronous behavior is only relevant for IO blocking functions. Meaning, that instead of waiting for an IO function, the event loop keeps running.
Seeing as JS is single threaded, CPU bounded computations take up the thread, and cannot be done asynchronously.
Your only recourse, then, is to create a native module that will do the calculation in a different thread for you, and then call a JS callback when it's done.