array of callbacks as Promise? - javascript

I am working through some old legacy code dealing with network requests using RPC/YUI library. It essentially creates tags to handle network requests. There are no promises for these.Also, because of IE11 support, we cannot use the native Promise object. Our build process does not utilize any NPM dependencies, so we cannot use any babel related polyfills.
There is a bug I am working on to fix that the argument ignoreError gets overwritten each time another function calls the same function....obviously! We have multiple functions calling this network request function library. Sometimes we want to ignore an error, sometimes we do not.
What is the ideal way to store the multiple requests made and their respective error callbacks so the appropriate item is called?
example:
var rpcUrl,
rpcRetries,
rpcIgnoreError;
// main function that sets some globals:
rpc: function(url, retries, ignoreError) {
rpcUrl = url;
rpcRetries = retries;
rpcIgnoreError = ignoreError;
this.doRpc();
},
// calls the YUI library to initialize network script:
doRpc: function() {
YAHOO.util.Get.script(rpcUrl, {
onFailure: function() {
this.callbackError(true);
},
timeout: 55000
});
},
// YUI callback
callbackError: function(retry) {
if (retry && rpcRetries > 0) {
rpcRetries = rpcRetries - 1;
this.doRpc();
} else {
// ** how do i know this error handling is for the script which failed?
if (!rpcIgnoreError) {
this.populateFormStatus(6);
}
}
},
now, we have multiple functions calling rpc() such as:
sendConfig: function() {
this.rpc(urlForEndpoint, 3, true);
},
sendUser: function() {
this.rpc(urlForEndpoint, 3, false);
},
sendWidget: function() {
this.rpc(urlForEndpoint, 3, false);
},
I am concerned making an array of callbacks will not guarantee that each item is handled with its respective handler.
I could do something like create a map constant:
var RPC_ERR_CB = {
sendConfig: false,
sendUser: true,
sendWidget: true
};
// and then in the onFailure callback, I can read the src of the script tag:
...
doRpc: function() {
YAHOO.util.Get.script(rpcUrl, {
onFailure: function() {
var hasCB = Object.keys(RPC_ERR_CB).some(function(item) {
return arguments[0].src.indexOf(RPC_ERR_CB[item]) <= 0;
});
if (hasCB) {
this.callbackError(true);
}
},
timeout: 55000
});
},
Hope this makes sense...THANKS!

You could pass the values into doRpc, then you can pass it to callbackError or handle it in doRpc (like your example code at the end). This will prevent the global variable from changing on you.

If you're not able to use Promises or ES6 Classes, your options become somewhat limited. If at all possible, I would recommend biting the bullet on getting a Babel transpilation process so you can take advantage of newer features without needing to drop IE11 support.
As it is now though, ideally you don't want to track every request in a global variable somewhere. You can handle each transaction independently by creating each request as a self-contained object:
function RpcRequest (url, retries, ignoreError) {
this.url = url
this.retries = retries
this.ignoreError = ignoreError
}
RpcRequest.prototype.send = function() {
YAHOO.util.Get.script(this.url, {
onFailure: function() {
this.callbackError(true);
},
timeout: 55000
});
}
RpcRequest.prototype.callbackError = function(retry) {
if (retry && this.retries > 0) {
this.retries = this.retries - 1;
this.send();
} else {
if (!this.ignoreError) {
// ...
}
}
}
// Somewhere else, initiate a request
var requestOne = new RpcRequest("http://blah", 3, false)
requestOne.send()
Something I noted when looking over your code: the code that's creating the request has no idea whether the request succeeded or not. And when you have an error, the calling context doesn't know anything about that error. I took a look at the library you mentioned, and it does appear to have some context that you can pass along.
If I were to rewrite this a little bit, I'd do something like this to bubble the error up to your calling context:
RpcRequest.prototype.send = function(callback) {
YAHOO.util.Get.script(this.url, {
onFailure: function(context) {
if( this.ignoreError ) {
context.ignoredError = true
callback(null, context);
return;
}
var retError = new Error('Failure doing something!');
retError.context = context;
callback(retError);
},
onSuccess: function(context) {
callback(null, context);
},
timeout: 55000
});
}
// Somewhere else in the code...
sendWidget: function() {
var request = new RpcRequest(urlForEndpoint, 3, false)
request.send(function(err, result) {
if( err ) {
console.error('Failed at doing a widget thing:', err.context);
// maybe even:
// throw err;
return;
}
if( result.ignoredError ) {
console.warn('Ignored an error on the widget thing:', result);
return;
}
console.log('Success on the widget thing!', result);
})
}

Related

How to end a script at any given time in nodejs

I am working on a small chat app backend in NodeJS.
I am trying to make a plugin system for it, Is there some way that I can end the script any time I want.
My code
pluginLoadder.js
/**
* Starts to load plugins
* #param {function} cb Runs after all plugins are loaded
*/
function startPlugins(cb) {
fs.readdir(pluginPath, function(err, files) {
if (err !== null) {
console.error(err);
} else {
const pluginFileREGEXP = /.+\.js$/;
files = files.filter(function(value) {
return pluginFileREGEXP.test(value);
});
files.forEach(function(val) {
try {
const tempPlugin = require(pluginPath +'/'+ val );
const func = {
events: events,
log: log,
};
tempPlugin.main(func);
events.emit('test');
} catch (e) {
if (e instanceof TypeError) {
log(`ERROR: ${val} is not a talker-backend plugin\n
E: ${e}`, 'error');
} else {
log(e, 'error');
}
}
});
cb();
}
});
}
./plugins/spamer.js
//* This is a plugin thats in the directory ./plugins
/* I want to stop this script through pluginLoader.js
*/
function main(func) {
const {events, log} = func;
setInterval(function (){
log('test', 'event');
}, 1000)
}
module.exports = {
main: main
};
In most cases you could use process.exit which takes in an exit code as an integer parameter (Node.js interprets non-zero codes as failure, and an exit code of 0 as success).
Then if you want to specifically target a process (assuming you are running multiple ones) you could use process.kill which takes process pid as a parameter.
Finally you could use process.abort which immediately kills Node.js without any callback executed and with a possibility to generate a core file.
To sum up:
// You would mostly use
process.exit(exitCode); // where exitCode is an integer
// or
process.kill(processPid);
// or
process.abort();
Just call it anytime you need to exit your program in your code.

IndexedDB's callbacks not being executed inside the 'fetch' event of a Service Worker

I'm trying to do a couple of things in the IndexedDB database inside the 'fetch' event of a service worker, when the aplication asks the server for a new page. Here's what I'm going for:
Create a new object store (they need to be created dynamically, according to the data that 'fetch' picks up);
Store an element on the store.
Or, if the store already exists:
Get an element from the store;
Update the element and store it back on the store.
The problem is that the callbacks (onupgradeneeded, onsuccess, etc) never get executed.
I've been trying with the callbacks inside of each other, though I know that may not be the best approach. I've also tried placing an event.waitUntil() on 'fetch' but it didn't help.
The 'fetch' event, where the function registerPageAccess is called:
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request)
.then(function (response) {
event.waitUntil(function () {
const nextPageURL = new URL(event.request.url);
if (event.request.destination == 'document') {
if (currentURL) {
registerPageAccess(currentURL, nextPageURL);
}
currentURL = nextPageURL;
}
}());
/*
* some other operations
*/
return response || fetch(event.request);
})
);
});
registerPageAccess, the function with the callbacks.
I know it's plenty of code, but just look at secondRequest.onupgradeneeded in the 5th line. It is never executed, let alone the following ones.
function registerPageAccess(currentPageURL, nextPageURL) {
var newVersion = parseInt(db.version) + 1;
var secondRequest = indexedDB.open(DB_NAME, newVersion);
secondRequest.onupgradeneeded = function (e) {
db = e.target.result;
db.createObjectStore(currentPageURL, { keyPath: "pageURL" });
var transaction = request.result.transaction([currentPageURL], 'readwrite');
var store = transaction.objectStore(currentPageURL);
var getRequest = store.get(nextPageURL);
getRequest.onsuccess = function (event) {
var obj = getRequest.result;
if (!obj) {
// Insert element into the database
console.debug('ServiceWorker: No matching object in the database');
const addRes = putInObjectStore(nextPageURL, 1, store);
addRes.onsuccess = function (event) {
console.debug('ServiceWorker: Element was successfully added in the Object Store');
}
addRes.onerror = function (event) {
console.error('ServiceWorker error adding element to the Object Store: ' + addRes.error);
}
}
else {
// Updating database element
const updRes = putInObjectStore(obj.pageURL, obj.nVisits + 1, store);
updRes.onsuccess = function (event) {
console.debug('ServiceWorker: Element was successfully updated in the Object Store');
}
updRes.onerror = function (event) {
console.error('ServiceWorker error updating element of the Object Store: ' + putRes.error);
}
}
};
};
secondRequest.onsuccess = function (e) {
console.log('ServiceWorker: secondRequest onsuccess');
};
secondRequest.onerror = function (e) {
console.error('ServiceWorker: error on the secondRequest.open: ' + secondRequest.error);
};
}
I need a way to perform the operations in registerPageAccess, which involve executing a couple of callbacks, but the browser seems to kill the Service Worker before they get to occur.
All asynchronous logic inside of a service worker needs to be promise-based. Because IndexedDB is callback-based, you're going to find yourself needing to wrap the relevant callbacks in a promise.
I'd strongly recommend not attempting to do this on your own, and instead using one of the following libraries, which are well-tested, efficient, and lightweight:
idb-keyval, if you're okay with a simple key-value store.
idb if you're need the full IndexedDB API.
I'd also recommend that you consider using the async/await syntax inside of your service worker's fetch handler, as it tends to make promise-based code more readable.
Put together, this would look roughly like:
self.addEventListener('fetch', (event) => {
event.waitUntil((async () => {
// Your IDB cleanup logic here.
// Basically, anything that can execute separately
// from response generation.
})());
event.respondWith((async () => {
// Your response generation logic here.
// Return a Response object at the end of the function.
})());
});

Create custom event, like ionicPlatform.ready() or document.ready()

I have an Ionic app in which I use a database. I want to fill this database with the contents of a file.
This part I got working. I want to create a DB.ready() event, much like the $ionicPlatform.ready() or document.ready(), as I need to wait until the database is loaded until I query it.
I am fairly new to Ionic, and to the concept of Promises, so it might be something simple.
I've gotten it to work in Android, but iOS keeps returning an error in the query with "someTablename does not exist". I've placed multiple console.log(), and according to those everything is fine.
Could anyone tell me which part I did incorrect, or another method if that is more common in this situation (again, I'm new, so don't know what is common)?
I expected to get "query" logged every query, but it doesn't do that, is that significant?
// L35_DB - Databaseclass for apps
.factory('L35_DB', ['$ionicPlatform','$cordovaFile','$cordovaSQLite', function($ionicPlatform, $cordovaFile,$cordovaSQLite) {
var L35_DB = {db_start : false};
//-------------------------------------
DB_READY = new Promise(function(resolve,reject){
console.log("query");
if( L35_DB.db_start ){console.log("b"); resolve("Stuff worked!"); }
else{
var filename='fileWithDB.db';
$ionicPlatform.ready(function() {
if( window.cordova ){
return window.plugins.sqlDB.copy(filename, 0,
function(info){ loadDB(filename).then( function(){ console.log("First load", info); resolve("DB loaded?"); }) },
function(info){ loadDB(filename).then( function(){ console.log("Other loads", info); resolve("DB loaded?"); }) }
);
}
});
}
});
//-------------------------------------
// Load the file
function loadDB(filename){
var loading = new Promise(function(resolve,reject){
db = window.sqlitePlugin.openDatabase(
{name: filename, location: 'default'},
function(){
console.log("loadDB success"); // <--- fired
L35_DB.db_start = true; // true, so next call we don't do all this
resolve("DB ready loading");
},
function(err){ reject(err);}
);
});
return loading;
}
//-------------------------------------
// Query -
var _query = function(query,values){
if( !L35_DB.db_start ){
console.error("DB not init");
return false;
}
else if( window.cordova ){
values = values || [];
var actualQuery = new Promise(function(resolve,reject){
db.executeSql(query, values, resolve, reject);
})
return actualQuery;
}
}
//-------------------------------------
return {
query : _query
};
}])
Throughout my app I do:
DB_READY.then(function () {
L35_DB.query("SELECT * FROM systems").then(function (result) {
// Something something something darkside
})
})
After a lot of testing and digging, turns out window.plugins.sqlDB.copy() was the culprit.
The 2nd value, location, can be changed. It defaults to 0, but for iOS it has to be 2. After this change, everything work exactly as expected.
This function should preload the database for Android and iOS, assumed a bit too early it actually did.

Struggling with Q.js promises

I am sure I am missing something obvious but I can't seem to make heads or tails of this problem. I have a web page that is being driven by javascript. The bindings are being provided by Knockout.js, the data is coming down from the server using Breeze.js, I am using modules tied together with Require.js. My goal is to load the html, load the info from Breeze.js, and then apply the bindings to show the data to the user. All of these things appear to be happening correctly, just not in the correct order which is leading to weird binding errors. Now on to the code.
I have a function that gets called after the page loads
function applyViewModel() {
var vm = viewModel();
vm.activate()
.then(
applyBindings(vm)
);
}
This should call activate, wait for activate to finish, then apply bindings....but it appears to be calling activate, not waiting for it to finish and then runs applybindings.
activate -
function activate() {
logger.log('Frames Admin View Activated', null, 'frames', false);
return datacontext.getAllManufacturers(manufacturers)
.then(function () {
manufacturer(manufacturers()[0]);
}).then(function () {
datacontext.getModelsWithSizes(modelsWithSizes, manufacturers()[0].manufacturerID())
.then(datacontext.getTypes(types));
});
}
datacontext.getAllManufacturers -
var getAllManufacturers = function (manufacturerObservable) {
var query = entityQuery.from('Manufacturers')
.orderBy('name');
return manager.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
function querySucceeded(data) {
if (manufacturerObservable) {
manufacturerObservable(data.results);
}
log('Retrieved [All Manufacturer] from remote data source',
data, true);
}
};
datacontext.getModelsWithSizes -
var getModelsWithSizes = function (modelsObservable, manufacturerId) {
var query = entityQuery.from('Models').where('manufactuerID', '==', manufacturerId)
.orderBy('name');
return manager.executeQuery(query)
.then(querySucceeded)
.fail(queryFailed);
function querySucceeded(data) {
if (modelsObservable) {
for (var i = 0; i < data.results.length; i++) {
datacontext.getSizes(data.results[i].sizes, data.results[i].modelID());
// add new size function
data.results[i].addNewSize = function () {
var newValue = createNewSize(this.modelID());
this.sizes.valueHasMutated();
return newValue;
};
}
modelsObservable(data.results);
}
log('Retrieved [Models With Sizes] from remote data source',
data, false);
}
};
Any help on why this promise isn't working would be appreciated, as would any process to figure it out so I can help myself the next time I run into this.
A common mistake when working with promises is instead of specifying a callback, you specify the value returned from a callback:
function applyViewModel() {
var vm = viewModel();
vm.activate()
.then( applyBindings(vm) );
}
Note that when the callback returns a regular truthy value (number, object, string), this should cause an exception. However, if the callback doesn't return anything or it returns a function, this can be tricky to locate.
To correct code should look like this:
function applyViewModel() {
var vm = viewModel();
vm.activate()
.then(function() {
applyBindings(vm);
});
}

Can this old-style ajax call be easily done with jquery?

I'm working with some pretty old code and the following is being used to monitor session status. If the user is inactive for X minutes (determined by check_session.php), they are logged out.
The server side stuff works fine. Actually, the existing javascript appears to work OK as well, but looks like it needs cleaning up.
Here's the existing javascript:
function checkSessionStatus()
{
session_http.open('GET', '/check_session.php', true);
session_http.onreadystatechange = handleSessionHttpResponse;
session_http.send(null);
}
function handleSessionHttpResponse()
{
if (session_http.readyState == 4)
{
results = session_http.responseText;
if (results == 'inactive')
{
window.location='/logout.php';
document.getElementById('session_divbox').innerHTML = results;
}
}
}
function get_session_HTTPObject()
{
var xml_session_http;
if (!xml_session_http && typeof XMLHttpRequest != 'undefined')
{
try
{
xml_session_http = new XMLHttpRequest();
}
catch (e)
{
xml_session_http = false;
}
}
return xml_session_http;
}
var session_http = get_session_HTTPObject();
function init_page_header()
{
window.setInterval( 'checkSessionStatus();', 30000);
}
This seems incredibly long for what it is doing.
I am still learning jquery and am able to do some basic ajax calls like this one, which places a returned value in a div:
$(document).ready(function()
{
$('#users_online').load('/show_users_online.php');
var refreshId = setInterval(function()
{
$('#users_online').load('/show_users_online.php');
}, 2000);
$.ajaxSetup({ cache: false });
});
The issue with the first bit of code is that it returns a value of 'inactive', which is then acted on by the client (window redirect).
Is it possible to do this in Jquery without winding up with dozens of lines of code? I may already know how to do this and am not seeing the forest for the trees -- some guidance here is appreciated.
Even if its very vampiric question style, should look like
$.get('/check_session.php', function( data ) {
if( data === 'inactive' ) {
window.location='/logout.php';
document.getElementById('session_divbox').innerHTML = data;
}
});

Categories