PWA Service worker match against specific cache timing issue - progressive-web-apps

Fairly new to SW and Promises (and I'm not that good with JS either!) so I might be missing something obvious.
Users have different sections that they can add and delete and the sections contain images they would add to those sections so I want to use SW to cache those images but want to put them in a section numbered cache so if they delete a section I can easily just delete that cache.
Following some research and tutorials I got this working in the 'fetch' event listener:
var cacheName = `mycache-${id}`;
event.respondWith(
caches.match(event.request, { 'ignoreSearch': true }).then(function (resp) {
return resp || fetch(event.request).then(function (response) {
return caches.open(cacheName).then(function (cache) {
cache.put(event.request, response.clone());
return response;
})
})
})
);
But obviously that's searching all the caches so I assumed it would be more efficient to search the only cache it needs to however this doesn't seem to work, seems like it's hitting the fetch before it can respond with what it might have found in a cache?
var cacheName = `mycache-${id}`;
event.respondWith(
caches.open(cacheName).then(function (cache) {
console.log("Opened cache");
cache.match(event.request, { 'ignoreSearch': true }).then(function (resp) {
console.log("Tried to find a match");
return resp || fetch(event.request).then(function (response) {
return caches.open(cacheName).then(function (cache) {
cache.put(event.request, response.clone());
return response;
})
})
})
})
);
It is putting the images in the named cache so am I doing something stupid here, I understand that Promises are async but my understanding was the '.then' will wait for a response before it continues.

Ok well it appears it was a simple case of a missing return!
This line:
cache.match(event.request, { 'ignoreSearch': true }).then(function
(resp) {
should have been
return cache.match(event.request, { 'ignoreSearch': true
}).then(function (resp) {

Related

Why items are not being pushed in array

I am using MongoseDB in order to receive some information about an item. When i try to search for it, it finds it with no trouble, but for some reasons this function is not pushing them into my array. I think this might be because of some async functions and that the console.log() is triggered before any item is being pushed in there.
const getOrders = function(allOrders){
let promise = new Promise((succ, fail)=>{
let ordersTodisplay = []
for (let order of allOrders) {
if (!(order.orderId === null || order.orderItem === null)){
postMong.findById(order.orderItem, function (err, item) {
ordersTodisplay.push(item)
})
}
}
if(ordersTodisplay.length > 0){
succ(ordersTodisplay)
} else{
fail("no items")
}
})
return promise
}
router.get('/accountpage',function(req,res){
const userDB = req.session.username
if (userDB !== undefined && userDB){
userForm.findOne({ username : userDB }, function (err, user) {
const userOrders = user.userOrders;
if (userOrders.length > 1) {
getOrders(userOrders).then((result)=>{console.log(result)}, (fail)=>{console.log(fail)})
res.render('../view/accountpage',{username: userDB,orders: itemsToDisplay});
}
else{
res.render('../view/accountpage',{username: userDB,orders: "There are no orders"});
}
});
} else {
res.redirect("/login")
}
});
The result is : no items
You have to for the database call to complete and then push the data in the array like this, using async-await:
const getOrders = function(allOrders){
let promise = new Promise(async (succ, fail)=>{
let ordersTodisplay = []
for (let order of allOrders) {
if (!(order.orderId === null || order.orderItem === null)){
await postMong.findById(order.orderItem, function (err, item) {
ordersTodisplay.push(item)
})
}
}
if(ordersTodisplay.length > 0){
succ(ordersTodisplay)
} else{
fail("no items")
}
})
return promise
}
Your code is quite nested and that makes it hard to reason about what is happening.
To break down your code, you:
get a single user that has several order IDs referenced
load each order
respond with those orders (although you return itemsToDisplay that doesn't seem to be defined anywhere, so I'm a bit confused)
I'd try to capture that logical pattern in the code. A good trick is returning early to make the code less nested and interdependent:
router.get('/accountpage', function(req,res){
const userDB = req.session.username;
if (!userDB){
res.redirect("/login");
return;
}
loadUserOrders(userDB)
.then(function(orders) {
if(orders.length > 0) {
res.render('../view/accountpage', {username: userDB,orders: orders});
return;
}
// Note: consider returning just the empty array here, that already implies no orders
res.render('../view/accountpage', {username: userDB, orders: "There are no orders"});
})
.catch(function(error) {
//TODO: render error -- case not covered by your code
});
});
// Because this is an async function you can now await inside it, meaning no need for the callback syntax for mongoose
async function loadUserOrders(username) {
const user = await userForm.findOne({ username: username });
// Promise.all runs in parallel several promises and returns an array containing their results
// .map here turns the list of userOrders into a list of promises getting each order
return await Promise.all(user.userOrders.map((userOrder) => postMong.findById(userOrder.orderItem));
}
Notice how this code highlights that you are not explicitly handling the error case from loading orders.
You can further simplify this by using something like express-async-handler which will let your endpoint function be async as well:
const asyncHandler = require('express-async-handler');
router.get('/accountpage', asyncHandler(async function(req,res){
const userDB = req.session.username;
if (!userDB){
res.redirect("/login");
return;
}
// Note: there is no try-catch around loadUserOrders right now, so errors will result in a 500 courtesy of express-async-handler -- better than before
const orders = await loadUserOrders(userDB);
if(orders.length > 0) {
res.render('../view/accountpage', {username: userDB,orders: orders});
return;
}
// Note: consider returning just the empty array here, that already implies no orders
res.render('../view/accountpage', {username: userDB, orders: "There are no orders"});
}));
I think using async/await syntax all the way through leaves the code more consistent and easier to reason about than the mix of callbacks, new Promise and await that was suggested in another answer. In the end, the endpoint is not very complex, and I think the code can reflect that.

my delete method of axios didn't work while post method work

I am making likes, dislikes function.
everything is working fine, except this delete method 😐
at React :
const goDownLike = async () => {
const variables = {
fromWhom: user.userData._id,
toWhat: toWhat
};
await axios.delete('/api/heart/downLike', variables);
}
at Node.js :
router.delete('/downLike', (req, res) => {
Like.findOneAndDelete(req.body).exec((err, result) => {
if (err) return res.status(400).json({ success: false, err });
res.status(200).json({ success: true });
});
});
when I clicked heart everytime, it seemed like it deleted somebody's likes including mine 😐
so I changed like this:
router.delete('/downLike', (req, res) => {
const { fromWhom, toWhat } = req.body;
Like.findOneAndDelete({ fromWhom: fromWhom, toWhat: toWhat }).exec(
(err, result) => {
if (err) return res.status(400).json({ success: false, err });
res.status(200).json({ success: true });
}
);
});
but this is actually do not delete any likes, even mine!
I changed from this
Like.findOneAndDelete
to this
Like.deleteOne
and this is not working, too. it delete nothing.
so I changed delete method to post method
and finally it worked. 😐
my likes was deleted and somebody's heart was not.
but I don't want to use post method instead of delete method 😐
I want to keep the rules of RESTful.
What should I do?
What did I missed?
thanks for your helps.
The reason it works with POST and not DELETE is that the second parameter for axios.post() is the body, whereas the second parameter for axios.delete() is the axios config object. This is presumably because there has been some disagreement and change over time as to whether DELETE requests can or should have a body, and what should happen if they do.
You can pass data in the config object, as seen here.

Getting retry limit exceeded error on uploading a few images to Google Storage

I'm not sure what's going on, because I don't get this issue, but a lot of my users report this issue. I have a page that uploads multiple files (the error is happening at 5). Several users report getting the error "storage/retry-limit-exceeded". What's going on? Please help.
In the code below, each error[i][j] gets the same code, which is "storage/retry-limit-exceeded".
My code looks like:
function SubmitFiles(){
pending=true;
let promises = partsofprobs.map((x,i) => {
return x.map((y,j) => FileUpload(i,j));
});
Promise.all(promises.map(x=>Promise.all(x))).then((values)=>{
//DO SOME STUFF WITH THE VALUES -- code removed for brevity, since the error is thrown by the other function.
});
}
function FileUpload(i,j){
return new Promise((resolve, reject) => {
let file=newpicdata[i][j];
if(file){
currentupload[i][j]=true;
let storageRef = storage.ref();
let fileRef = storageRef.child(examid+'/'+i+'/'+j+'/'+$currentUser.uid+'/'+newfiles[i][j][0].name);
let uploadTask = fileRef.putString(file, 'data_url');
uploadTask.on('state_changed',
function(snapshot) {
// Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
let uploadedpercent = (snapshot.bytesTransferred / snapshot.totalBytes)*100;
console.log('Upload is ' + uploadedpercent + '% done');
switch (snapshot.state) {
case 'paused':
console.log('Upload is paused');
break;
case 'running':
console.log('Upload is running');
break;
}
}, function(err) {
console.log("error code: ", err.code);
error[i][j]=err.code;
currentupload[i][j]=false;
reject();
}, function() {
// Upload completed successfully, now we can get the download URL
uploadTask.snapshot.ref.getDownloadURL().then(function(downloadURL) {
uploadedpicsurls[i][j]=downloadURL;
success[i][j]=true;
currentupload[i][j]=false;
console.log('File available at', downloadURL);
resolve({url:downloadURL,graded:false,score:null,problemindex:i,part:j,grader:null,notes:[],outof:partsofprobs[i][j],beinggradedby:[]});
});
});
}else{
resolve(existingrecord ? existingrecord.solutions[i].parts[j]:{url:null,graded:false,score:0,problemindex:i,part:j,grader:null,notes:[],outof:partsofprobs[i][j],beinggradedby:[]});
}
})
}

How to perform a redirect inside a Promise inside a fetch()

Is this the proper way to redirect inside a Promise? Does this cause any adverse effects? Possible memory leak?
.then(function (response ){
if( response.redirected){
location.href = response.url;
}
return response.json();
})
.then(function (record) {
console.log("record update:",record);
onRecordUpdate(record);
})
.then(function (response ){
if( response.redirected){
location.href = response.url;
}
return response.json();
})
.then(function (record) {
console.log("record update:",record);
onRecordUpdate(record);
})
Basically, it will not run the codes that after location change. Sometimes it can works if the browser gets slow but theorically it shouldn't work. So my opinion you should run onRecordUpdate(record) function before location change to avoid any silent error.
As #nils says, you will not get any memory leak because it will refresh the page and state will be cleaned.

How to get Meteor.Call to return value for template?

I've tried to understand this post regarding this concept, however, I'm failing to get it. I have the following simple setup:
/server/test.js
Meteor.methods({
abc: function() {
var result = {};
result.foo = "Hello ";
result.bar = "World!";
return result;
}
});
/client/myapp.js
var q = Meteor.call('abc');
console.log(q);
This structure returns to the console undefined.
If I change the myapp.js file to:
Meteor.call('abc', function(err, data) {
!err ? console.log(data) : console.log(err);
}
I receive the Object in my console.
Ideally this is what I'd like to be able to do, but it doesn't work, stating in the console: Cannot read property 'greeting' of undefined
/client/myapp.js
var q = Meteor.call('abc');
Template.hello.greeting = function() {
return q.foo;
}
Any help in passing the data from the server object into the template would be greatly appreciated. I'm still learning JavaScript & Meteor.
Thanks!
From the Meteor.call documentation:
On the client, if you do not pass a callback and you are not inside a stub, call will return undefined, and you will have no way to get the return value of the method. That is because the client doesn't have fibers, so there is not actually any way it can block on the remote execution of a method.
So, you'll want to do it like this:
Meteor.call('abc', function(err, data) {
if (err)
console.log(err);
Session.set('q', data);
});
Template.hello.greeting = function() {
return Session.get('q').foo;
};
This will reactively update the template once the data is available.
This happens because Npm.require has Async behavior. That's the reason that you have to write a callback for Meteor.call.
But there is a solution, just use install(mrt add npm) and you'll get a function named Meteor.sync(//...) with this you can do both games: sync and async in your Meteor.call().
Reference: http://www.sitepoint.com/create-a-meteor-app-using-npm-module/
You can get the return value of a Meteor method for use in a template by using a reactive variable. Check out the working demonstration on Meteorpad
I went for a ghetto solution. But, it works for me, which is what matters, to me. Below is my code, which, in concept, I think, solves OP's problem.
In the client's main.js:
Meteor.setInterval(function() {
confirmLogin();
}, 5000);
This runs the confirmLogin() function every five seconds.
The confirmLogin function (in the client's main.js):
function confirmLogin() {
Meteor.call('loggedIn', function (error, result) {
Session.set("loggedIn", result);
});
}
The loggedIn method (in the server's main.js):
loggedIn: function () {
var toReturn = false;
var userDetails = Meteor.user();
if (typeof userDetails["services"] !== "undefined") {
if (typeof userDetails["services"]["facebook"] != "undefined") {
toReturn = true;
}
}
return toReturn;
},
The relevant helper:
loggedIn: function () {
return Session.get("loggedIn");
}