why can service worker respondWith() return a fetch object instead of a real response? - fetch

I was reading the MDN docs, and got confused why event.respondWith can have a fetch object returned? Isn't the actual request initiator expecting a response instead of a fetch?
addEventListener('fetch', event => {
// Prevent the default, and handle the request ourselves.
event.respondWith(async function() {
// Try to get the response from a cache.
const cachedResponse = await caches.match(event.request);
// Return it if we found one.
if (cachedResponse) return cachedResponse;
// If we didn't find a match in the cache, use the network.
return fetch(event.request);
}());
});

The actual request initiator is not expecting a response. It is expecting a promise that resolves into a response. The MDN docs say exactly that:
The respondWith() method of FetchEvent prevents the browser's default
fetch handling, and allows you to provide a promise for a Response
yourself.
You are not returning a fetch object here when you call fetch(event.request). You are calling the fetch method which returns a promise that resolves into a response.
You can return any promise that resolves to a response here, like so:
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve(/* { Fake Response Object } */);
}, 1500);
});

Related

Firebase cloud function error: Maximum call size stack size exceeded

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)
}
});
});

event.passThroughOnException sends requests to origin, but without POST data

I thought that event.passThroughOnException(); should set the fail open strategy for my worker, so that if an exception is raised from my code, original requests are sent to my origin server, but it seems that it’s missing post data. I think that’s because the request body is a readable stream and once read it cannot be read again, but how to manage this scenario?
addEventListener('fetch', (event) => {
event.passThroughOnException();
event.respondWith(handleRequest(event));
});
async function handleRequest(event: FetchEvent): Promise<Response> {
const response = await fetch(event.request);
// do something here that potentially raises an Exception
// #ts-ignore
ohnoez(); // deliberate failure
return response;
}
As you can see in the below image, the origin server did not receive any body (foobar):
Unfortunately, this is a known limitation of passThroughOnException(). The Workers Runtime uses streaming for request and response bodies; it does not buffer the body. As a result, once the body is consumed, it is gone. So if you forward the request, and then throw an exception afterwards, the request body is not available to send again.
Did a workaround by cloning event.request, then add a try/catch in handleRequest. On catch(err), send the request to origin using fetch while passing the cloned request.
// Pass request to whatever it requested
async function passThrough(request: Request): Promise<Response> {
try {
let response = await fetch(request)
// Make the headers mutable by re-constructing the Response.
response = new Response(response.body, response)
return response
} catch (err) {
return ErrorResponse.NewError(err).respond()
}
}
// request handler
async function handleRequest(event: FetchEvent): Promise<Response> {
const request = event.request
const requestClone = event.request.clone()
let resp
try {
// handle request
resp = await handler.api(request)
} catch (err) {
// Pass through manually on exception (because event.passThroughOnException
// does not pass request body, so use that as a last resort)
resp = await passThrough(requestClone)
}
return resp
}
addEventListener('fetch', (event) => {
// Still added passThroughOnException here
// in case the `passThrough` function throws exception
event.passThroughOnException()
event.respondWith(handleRequest(event))
})
Seems to work OK so far. Would love to know if there are other solutions as well.

Service Worker - TypeError: Request failed

I used service worker to cache the resource from the other domain. I get this error "TypeError: Request failed serivce-worker.js:12" I don't know why this error is occurring.
service-worker.js
var cacheNames=['v1'];
var urlsToPrefetch=['file from other domain'];
self.addEventListener('install', function (event) {
event.waitUntil(
caches.open(cacheNames).then(function(cache) {
console.log('Service Worker: Caching Files');
cache.addAll(urlsToPrefetch.map(function (urlToPrefetch) {
console.log(urlToPrefetch);
return new Request(urlToPrefetch, {mode: 'no-cors'});
})).catch(function(error){
console.log(error);
});
})
);
});
self.addEventListener('fetch', function(event) {
console.log('Service Worker: Fetching');
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
return response;
}
return fetch(event.request);
}
)
);
});
This is a side-effect of dealing with opaque responses (those fetched with mode: 'no-cors'). Here's an excerpt from this longer answer:
One "gotcha" that developer might run into with opaque responses involves using them with the Cache Storage API. Two pieces of background information are relevant:
The status property of an opaque response is always set to 0, regardless of whether the original request succeeded or failed.
The Cache Storage API's add()/addAll() methods will both reject if the responses resulting from any of the requests have a status code that isn't in the 2XX range.
From those two points, it follows that if the request performed as part of the add()/addAll() call results in an opaque response, it will fail to be added to the cache.
You can work around this by explicitly performing a fetch() and then calling the put() method with the opaque response. By doing so, you're effectively opting-in to the risk that the response you're caching might have been an error returned by your server.
const request = new Request('https://third-party-no-cors.com/', {mode: 'no-cors'});
// Assume `cache` is an open instance of the Cache class.
fetch(request).then(response => cache.put(request, response));

Need help understanding this promise and handling the error

I am saving some data into my Firebase database inside my Polymer element. All works fine. But as I person who's new to Promises I need help to understand what Promise.resolved() means here at then end of the method. Isn't the promise going through before that when .then is used? So what exactly this is doing? I looked around but can't find an example of resolved() with no value.
And how can I change this to have more familiar structure as below:
.then(function(snapshot) {
// The Promise was "fulfilled" (it succeeded).
}, function(error) {
// The Promise was rejected.
});
Here's the block and the Promise.resolved() taken from the documentation:
saveData : function() {
this.$.document.data = this.$.message.value;
this.$.document.save("/parent", "child").then(function() {
console.log('sent the event!!!!!!');
this.$.document.reset();
}.bind(this));
return Promise.resolve();
},
First you need to understand the basics of Promises.
Lets start from very basics -
A newly created es6 promise is in one of the following states:
resolved
rejected
pending --> waiting to either resolved or rejected
Lets create a sample Promise
var promise = new Promise(function(fulfill, reject) {
// Do some stuff and either fullfill or reject the promise
});
So above promise receives a callback function also called executor function with signature function(fullfill, reject).
A newly created promise also has a very important property function called then used for chaining and controlling the logic flows.
then takes two optional callback parameters onFulfilled and onRejected.
Inside this executor function two things happens to indicate the outcome of promise -
fullfill method gets called with or without a value:
means operation completed successfully. If you call fulfill with a value then onFulfilled callback in then will receive that value, if you decided not to provide a value in fulfill call then onFulfilled will be called with a parameter undefined.
var promise = new Promise(function(fulfill, reject) {
// lets assume operation completed successfully
fulfill('Success');
});
promise.then(onFulfilled, onRejected);
function onFulfilled(result) {
console.log(result);
// Success will be printed
}
reject method gets called with or without a value:
Some problem occurred while performing the operation. You can decided whether pass some error message reject callback to indicate the error occurred to end user.
var promise = new Promise(function(fulfill, reject) {
// lets assume operation did not complete successfully
reject(new Error('Error'));
});
promise.then(onFulfilled, onRejected);
function onRejected(error) {
console.log(error.message);
// Error message will be printed
}
Now lets talk about Promise.resolve.
At the top you learned how to create promise through the constructor.
var promise = new Promise(function (fulfill, reject) {
fulfill('Success value');
});
// Now: Promise.resolve
// Exactly does the same thing as above code
var promise = Promise.resolve('Success value');
Similarly comes Promise.reject -
var promise = new Promise(function (fulfill, reject) {
reject(new Error('Error VALUE'));
});
var promise = Promise.reject(new Error('Error VALUE'));
In your case save seems to be returning a promise already and internally that promise may be calling either fulfill or reject method so you don't need to call Promise.resolve(). You just need to get the values returned by that promise either fulfilled value or rejected value in the then method.
saveData : function() {
this.$.document.data = this.$.message.value;
// return this promise
return this.$.document.save("/parent", "child");
}
saveData()
.then(function() {
console.log('sent the event!!!!!!');
this.$.document.reset();
}.bind(this));
I hope it makes things about promises somewhat clearer.
If you're trying to be able to do obj.saveData().then(...), then you can return the inner promise like this:
saveData : function() {
this.$.document.data = this.$.message.value;
// return this promise
return this.$.document.save("/parent", "child").then(function() {
console.log('sent the event!!!!!!');
this.$.document.reset();
}.bind(this));
}

NodeJS: problem with request asynchronous, synchronous, api

I have problem with trying to turn asynchronous function into synchronous.
here is a method from class:
doPost: function(call, data) {
var uri = 'http://localhost/api/'+call;
var api = http.createClient(80, 'localhost');
var domain = 'localhost';
var request = api.request("POST", uri,
{'host' : domain,
'Content-Type' : 'application/x-www-form-urlencoded',
"User-Agent": this.userAgent,
'Content-Length' : data.length
});
request.write(data);
request.end();
request.on('response', function (response) {
response.on ('data', function (chunk) {
sys.puts(chunk);
try {
var result = JSON.parse(chunk);
//------------ the problem
return HOW_TO_RETURN_RESULT;
//------------ /the problem
}catch (err) {
return {'ok': 0, 'err': err}
}
});
});
},
Want to use this function in this way:
result = obj.doPost('getSomeData.php', '&data1=foo&data2=bar');
Reagards
Tom
Simply use callback.
obj.doPost('getSomeData.php', '&data1=foo&data2=bar', function(data) {
result = data;
});
It is impossible to turn an asynchronous function into a synchronous one.
It simply cannot be done.
Instead, you must pass a callback to your function and receive the "return value" in async fashion.
In theory though, you could write some code to block your function from returning until some condition is met (ie, until the async operation is complete), but that would also require the program to be able do other things on another thread while the block is executing, which is probably not possible in node. And even if it were, it would be a world class antipattern and crime against node.js and all things evented and probably summon a velociraptor.
Conclusion: Learn how to work with asynchronous code. Also, you might be interested in reading this question/answer from yesterday (or at least the answer; the question is not very well phrased).

Resources