Service Workers and IndexedDB - service-worker

In a simple JavaScript Service Worker I want to intercept a request and read a value from IndexedDB before the event.respondWith
But the asynchronous nature of IndexDB does not seem to allow this.
Since the indexedDB.open is asynchronous, we have to await it which is fine. However, the callback (onsuccess) happens later so the function will exit immediately after the await on open.
The only way I have found to get it to work reliably is to add:
var wait = ms => new Promise((r, j) => setTimeout(r, ms));
await wait(50)
at the end of my readDB function to force a wait until the onsuccess has completed.
This is completely stupid!
And please don't even try to tell me about promises. They DO NOT WORK in this circumstance.
Does anyone know how we are supposed to use this properly?
Sample readDB is here (all error checking removed for clarity). Note, we cannot use await inside the onsuccess so the two inner IndexedDB calls are not awaited!
async function readDB(dbname, storeName, id) {
var result;
var request = await indexedDB.open(dbname, 1); //indexedDB.open is an asynchronous function
request.onsuccess = function (event) {
let db = event.target.result;
var transaction = db.transaction([storeName], "readonly"); //This is also asynchronous and needs await
var store = transaction.objectStore(storeName);
var objectStoreRequest = store.get(id); //This is also asynchronous and needs await
objectStoreRequest.onsuccess = function (event) {
result = objectStoreRequest.result;
};
};
//Without this wait, this function returns BEFORE the onsuccess has completed
console.warn('ABOUT TO WAIT');
var wait = ms => new Promise((r, j) => setTimeout(r, ms));
await wait(50)
console.warn('WAIT DONE');
return result;
}

And please don't even try to tell me about promises. They DO NOT WORK in this circumstance.
...
...
...
I mean, they do, though. Assuming that you're okay putting the promise-based IndexedDB lookups inside of event.respondWith() rather than before event.respondWith(), at least. (If you're trying to do this before calling event.respondWith(), to figure out whether or not you want to respond at all, you're correct in that it's not possible, since the decision as to whether or not to call event.respondWith() needs to be made synchronously.)
It's not easy to wrap IndexedDB in a promise-based interface, but https://github.com/jakearchibald/idb has already done the hard work, and it works quite well inside of a service worker. Moreover, https://github.com/jakearchibald/idb-keyval makes it even easier to do this sort of thing if you just need a single key/value pair, rather than the full IndexedDB feature set.
Here's an example, assuming you're okay with idb-keyval:
importScripts('https://cdn.jsdelivr.net/npm/idb-keyval#3/dist/idb-keyval-iife.min.js');
// Call idbKeyval.set() to save data to your datastore in the `install` handler,
// in the context of your `window`, etc.
self.addEventListener('fetch', event => {
// Optionally, add in some *synchronous* criteria here that examines event.request
// and only calls event.respondWith() if this `fetch` handler can respond.
event.respondWith(async function() {
const id = someLogicToCalculateAnId();
const value = await idbKeyval.get(id);
// You now can use `value` however you want.
const response = generateResponseFromValue(value);
return response;
}())
});

Related

How to call asynchronous function before event.respondWith in Service Worker?

its not work.
self.addEventListener('fetch', async (event) => {
const url = await localforage.getItem('url'); // url change if page changed
if(event.request.url === url){
event.respondWith(handleFetch(event));
}
});
const handleFetch = async (event) => {
....
}
if i move url to inside event.responseWith. its work, but do every request, but i need only match url to fetch in service worker if not match then do nothing.
self.addEventListener('fetch', async (event) => {
event.respondWith(handleFetch(event));
});
const handleFetch = async (event) => {
const url = await localforage.getItem('url'); // url change if page changed
if(event.request.url === url){
....
}
}
This is intentional. Your decision as to whether or not to call event.respondWith() needs to be done synchronously, within the top-level execution of your fetch handler.
This allows you to do things like examine the request URL and headers synchronously, but it does preclude you from performing asynchronous lookups against things like IndexedDB.
If you can't transition your criteria to use something synchronous, then your best option is to call event.respondWith() unconditionally, and when the criteria is not met, use return fetch(event.request) to come as close as you could to the "default" behavior you'd get if you didn't respond at all. (That's basically what you're doing in the second example.)

How to properly Loop through SMS sending via Twilio Functions

I have Twilio Studio calling a Twilio Function and need it to send email to a variable list (small list) of emails. This question is mostly around looping through them as I can pass variables just fine. I have an array of emails to send a text to and am in a Twilio Function. But all examples I find online are about sending to just ONE. Part of me thinks this needs to be a Twilio Function calling another Twilio function (one loops, the other sends Emails)... but I can't figure out a way to do that. If I could contain it to one Twilio function, that would be great.
I have Twilio Studio calling a Twilio function. I need to keep this all on Twilio... so looping through via PHP and running functions one at a time through there doesn't work. I need this to run on Twilio's serverless setup.
Here's an example of what I have that works:
exports.handler = function(context, event, callback) {
// using SendGrid's v3 Node.js Library
// https://github.com/sendgrid/sendgrid-nodejs
const sgMail = require('#sendgrid/mail');
sgMail.setApiKey(context.SENDGRID_API_KEY);
const msg = {
to: 'me#example.com',
from: 'noreply#example.com',
templateId: 'my-id-goes-here',
dynamic_template_data: {
recipient_name: 'John Smith'
}
};
sgMail.send(msg).then(response => {
let twiml = new Twilio.twiml.MessagingResponse();
callback(null, twiml);
})
.catch(err => {
callback(err);
});
};
Here's me trying to loop through in similar fashion and failing
exports.handler = function(context, event, callback) {
const sgMail = require('#sendgrid/mail');
sgMail.setApiKey(context.SENDGRID_API_KEY);
var responder_emails = 'me#example.com,me+test1#example.com';
var emails_a = responder_emails.split(',');
emails_a.forEach(function(responder_email) {
const msg = {
to: responder_email,
from: 'noreply#example.com',
templateId: 'my-id-goes-here',
dynamic_template_data: {
recipient_name: 'John Smith'
}
};
sgMail.send(msg);
});
callback();
};
I can pass in multiple emails into a Twilio function... I'm just not sure how to loop through correctly.
Heyo. Twilio Evangelist here. 👋
In your first example, you rightfully waited for the send call to be done by using then. In your second example, you missed that. You run several send calls but immediately call callback without waiting.
A fixed (roughly prototyped version) could look as follows.
exports.handler = function(context, event, callback) {
const sgMail = require('#sendgrid/mail');
sgMail.setApiKey(context.SENDGRID_API_KEY);
var responder_emails = 'me#example.com,me+test1#example.com';
var emails_a = responder_emails.split(',');
Promise.all(emails_a.map(function(responder_email) {
const msg = {
to: responder_email,
from: 'noreply#example.com',
templateId: 'my-id-goes-here',
dynamic_template_data: {
recipient_name: 'John Smith'
}
};
return sgMail.send(msg);
})).then(function() {
callback();
}).catch(function(e) {
callback(e);
})
});
You have already an array of emails because you called split. You can use this array in combination with Array.map and Promise.all.
Map basically iterates over your array and lets you create a new array with whatever you return from the function inside of map. What the code above does is that it transforms [email, email] to [Promise, Promise]. The promises are the return value of sgMail.send.
Now, that you have an array holding promises that will resolve when sendgrid accepted your call, you can use Promise.all. This method waits for all the promises to be resolved (or rejected) and returns itself a new promise which you can use then with. When all sendgrid calls are done it's time to finish the function by calling the function callback.
Side note: this "map/Promise.all" trick performs all send grid calls in parallel. There might be situations where you want to call them one after another (saying you are doing a lot of calls and run into rate limiting).
Hope that helps and let me know how it goes. :)

Atomic update of Realtime Database from Google Cloud Functions

I use Google Cloud Functions to create an API endpoint for my users to interact with the Realtime Database.
The problem I have is that I'm not sure how the code works. I have a helper function doSomething that I need to call only once, but I have a suspicion that there are cases where it can be called twice or possibly more (when multiple users call the API at the same time and the update operation hasn't been processed by the DB yet). Is it possible? Does it mean I need to use a transaction method? Thank you!
DB structure
{
somePath: {
someSubPath: null
}
}
Google Cloud Functions code
const functions = require('firebase-functions')
const admin = require('firebase-admin')
const cors = require('cors')({origin: true});
admin.initializeApp(functions.config().firebase)
// API ENDPOINT
exports.test = functions.https.onRequest((req, res) => {
cors(req, res, () => {
admin.database().ref('/somePath/someSubPath').once('value')
.then(snapshot => {
const value = snapshot.val()
if (value) return res.status(400).send({ message: 'doSomethingAlreadyCalled' })
doSomething()
const updates = { '/somePath/someSubPath': true }
return admin.database().ref().update(updates)
.then(() => res.status(200).send({ message: 'OK' }))
})
.catch(error => res.status(400).send({ message: 'updateError' }))
})
})
// HELPERS
const doSomething = () => {
// needs to be called only once
}
I believe you were downvoted due to the above pseudocode not making complete sense and there being no log or output of what your code is actually doing in your question. Not having a complete picture makes it hard for us to help you.
Just Going from your structure in the question, your actual code could be calling twice due to function hoisting. Whenever I have this issue, I’ll go back to the api documentation and try to restructure my code from rereading.
HTH

Wait while request is running

Here is a problem. When I ran these code:
String responseText = null;
HttpRequest.getString(url).then((resp) {
responseText = resp;
print(responseText);
});
print(responseText);
In console:
{"meta":{"code":200},"data":{"username":"kevin","bio":"CEO \u0026 Co-founder of Instagram","website":"","profile_picture":"http:\/\/images.ak.instagram.com\/profiles\/profile_3_75sq_1325536697.jpg","full_name":"Kevin Systrom","counts":{"media":1349,"followed_by":1110365,"follows":555},"id":"3"}}
null
It running asynchronously. Is there JAVA way with synchronized method? That will be await while request is done?
I found only one tricky way to do it and its funny -- wait for three seconds:
handleTimeout() {
print(responseText);
}
const TIMEOUT = const Duration(seconds: 3);
new Timer(TIMEOUT, handleTimeout);
And of course it works with bugs. So any suggestions?
MattB way work well:
var req = new HttpRequest();
req.onLoad.listen((e) {
responseText = req.responseText;
print(responseText);
});
req.open('GET', url, async: false);
req.send();
First, I'm assuming you're using this as a client-side script and not server-side. Using HttpRequest.getString will strictly return a Future (async method).
If you absolutely must have a synchronous request, you can construct a new HttpRequest object and call the open method passing the named parameter: async: false
var req = new HttpRequest();
req.onLoad.listen((e) => print(req.responseText));
req.open('GET', url, async: false);
req.send();
However it is highly recommended that you use async methods for accessing network resources as a synchronous call like above will cause the script to block and can potentially make it appear as though your page/script has stopped responding on poor network connections.

ASP.NET MVC ajax chat

I built an ajax chat in one of my mvc website. everything is working fine. I am using polling. At certain interval i am using $.post to get the messages from the db. But there is a problem. The message retrieved using $.post keeps on repeating. here is my javascript code and controller method.
var t;
function GetMessages() {
var LastMsgRec = $("#hdnLastMsgRec").val();
var RoomId = $("#hdnRoomId").val();
//Get all the messages associated with this roomId
$.post("/Chat/GetMessages", { roomId: RoomId, lastRecMsg: LastMsgRec }, function(Data) {
if (Data.Messages.length != 0) {
$("#messagesCont").append(Data.Messages);
if (Data.newUser.length != 0)
$("#usersUl").append(Data.newUser);
$("#messagesCont").attr({ scrollTop: $("#messagesCont").attr("scrollHeight") - $('#messagesCont').height() });
$("#userListCont").attr({ scrollTop: $("#userListCont").attr("scrollHeight") - $('#userListCont').height() });
}
else {
}
$("#hdnLastMsgRec").val(Data.LastMsgRec);
}, "json");
t = setTimeout("GetMessages()", 3000);
}
and here is my controller method to get the data:
public JsonResult GetMessages(int roomId,DateTime lastRecMsg)
{
StringBuilder messagesSb = new StringBuilder();
StringBuilder newUserSb = new StringBuilder();
List<Message> msgs = (dc.Messages).Where(m => m.RoomID == roomId && m.TimeStamp > lastRecMsg).ToList();
if (msgs.Count == 0)
{
return Json(new { Messages = "", LastMsgRec = System.DateTime.Now.ToString() });
}
foreach (Message item in msgs)
{
messagesSb.Append(string.Format(messageTemplate,item.User.Username,item.Text));
if (item.Text == "Just logged in!")
newUserSb.Append(string.Format(newUserTemplate,item.User.Username));
}
return Json(new {Messages = messagesSb.ToString(),LastMsgRec = System.DateTime.Now.ToString(),newUser = newUserSb.ToString().Length == 0 ?"":newUserSb.ToString()});
}
Everything is working absloutely perfect. But i some messages getting repeated. The first time page loads i am retrieving the data and call GetMessages() function. I am loading the value of field hdnLastMsgRec the first time page loads and after the value for this field are set by the javascript.
I think the message keeps on repeating because of asynchronous calls. I don't know, may be you guys can help me solve this.
or you can suggest better way to implement this.
Kaivalya is correct about the caching, but I'd also suggest that your design could and should be altered just a tad.
I made a very similar app recently, and what I found was that my design was greatly enhanced by letting the controllers work with the fairly standard PRG pattern (post-redirect-get). Why enhanced? well, because POST methods are built to add stuff to an app, GET methods are supposed to be used to get information without side effects. Your polling should be just getting new messages w/o side effects.
So rather than your $.post call expecting data and handling the callback, what I'd recommend is having your controller expose a method for creating new chat messages via POST and then another method that get the last X chat messages, or the messages since a certain timestamp or whatever.
The javascript callback from the post action, then can update some variables (e.g. the last message id, timestamp of the last message, or even the whole URL of the next message based on the info contained in a redirect, whatever).
The $.post would fire only in response to user input (e..g type in a box, hit 'send') Then, you have (separately) a $.get call from jquery that's set up to poll like you said, and all it does is fetch the latest chat messages and it's callback updates the chat UI with them.
I got my answer here: ASP.NET AJAX CHAT
The names below i am referring to are from above link.
i think the actual problem was with the timestamp thing and asynchronous behaviour of $.post. after calling "GetMessages()" method, even if the previous request to retrive chat message was not complete anathor call to same method used to fire due to setting timeout for "GetMessages()" method outside the $.post method. In my question you can see that timeout for "GetMessages()" method is set outside the $.post method. Now i set the timeout for "GetMessages()" method inside the $.post method. so that next call to "GetMessages()" only occur after 3 seconds of completion of current $.post method. I have posted the code below.
var t;
function GetMessages() {
var LastMsgRec = $("#hdnLastMsgRec").val();
var RoomId = $("#hdnRoomId").val();
//Get all the messages associated with this roomId
$.post("/Chat/GetMessages", { roomId: RoomId, lastRecMsg: LastMsgRec }, function(Data) {
if (Data.LastMsgRec.length != 0)
$("#hdnLastMsgRec").val(Data.LastMsgRec);
if (Data.Messages.length != 0) {
$("#messagesCont").append(Data.Messages);
if (Data.newUser.length != 0)
$("#usersUl").append(Data.newUser);
$("#messagesCont").attr({ scrollTop: $("#messagesCont").attr("scrollHeight") - $('#messagesCont').height() });
$("#userListCont").attr({ scrollTop: $("#userListCont").attr("scrollHeight") - $('#userListCont').height() });
}
else {
}
t = setTimeout("GetMessages()", 3000);
}, "json");
}
I addition to that i also changed few things. As suggested by ignatandrei i placed $("#hdnLastMsgRec").val(Data.LastMsgRec); immediately after function(Data) {.
and also
as said by MikeSW i changed the data retrieval process. Previously i was extracting data on the basis of timespan(retrieve all the data associated with
this room id that has greater timespan than last data retrieved message timespan) but now i keep track of the messageid. Now i retrieve only those data that
has message id greater than last retrieved message id.
and guess what no repeataion and perfectly working chat application so far on my intranet.
I still got to see it's performance when deployed on internet.
i think it solved my problem.
i will still test the system and let u guys know if there is any problem.
By default $.post() caches the results
You can either call $.ajaxSetup ({ cache: false}); before JS GetMessages function call to ensure caching is disabled or change the $.post to $.ajax and set cache attribute to false. In the end $.post() is a short cut to this:
$.ajax({
type: 'POST',
url: url,
data: data,
success: success
dataType: dataType
});

Resources