I would like to know if it is somehow possible to check if an asynchronous operation in Javascript is still pending..
Because I am doing a database request on calling a specific URL... While the db call is still in progress, I want to stop any other incoming db-calls (which means, stop any further calls to that URL in case the db-request is still pending).
Is that somehow possible?
Because the database call takes up to minutes, and I don't want to launch another database-call while the first is still in progress.. The problem is, I somehow cannot figure out how to check if the call has started and is still in progress, because the response comes only after the .then() clause when the process has already finished.
this is my db-call function:
const getWriteIndex = async () => {
return Promise.all(someFunction1, someFunction2...).then(...) {
writeMessageObject = checkDocuments(...);
return Promise.resolve(writeMessageObject);
})).catch((err) => {
return Promise.reject(err);
});
}
This is my URL/Route Call function with express:
router.get("/v1/...", someMiddleware(), async function(req,res,next) {
if (read_cached() && initialised_read) {
res.setHeader('Content-Type', 'application/json');
res.json(readmsg_obj);
} else {
try {
//HOW CAN I CHECK HERE IF THE DB-CALL IS ALREADY IN PROGRESS?
readmsg_obj.message = '';
getReadIndex().then((message) => {
initialised_read = true;
readmsg_obj = {...message};
res.setHeader('Content-Type', 'application/json');
res.json(readmsg_obj);
}).catch((reject) => {
logger.error(`/../... : ${reject}`);
initialised_read = false;
res.status(500).send(reject);
});
} catch(err) {
logger.error(`/v1/... : ${err}`);
res.status(500).send(err);
};
}
});
hmm I found a workaround here:
https://ourcodeworld.com/articles/read/317/how-to-check-if-a-javascript-promise-has-been-fulfilled-rejected-or-resolved
so I wrote that function to check for promise stati, but I am still wondering if it's not somehow possible to query for static promise properties to get their actual state ;) (but weirdly, I didn't find any on the web).
const checkPendingRequest= (promise) => {
if (promise.isResolved) return promise;
// Set initial state
var isPending = true;
var isRejected = false;
var isFulfilled = false;
// Observe the promise, saving the fulfillment in a closure scope.
var result = promise.then(
function(v) {
isFulfilled = true;
isPending = false;
return v;
},
function(e) {
isRejected = true;
isPending = false;
throw e;
}
);
result.isFulfilled = function() { return isFulfilled; };
result.isPending = function() { return isPending; };
result.isRejected = function() { return isRejected; };
return result;
}
So I fixed my function for the request:
router.get("/v1/...", someMiddleware(), async function(req,res,next) {
if (read_cached() && initialised_read) {
res.setHeader('Content-Type', 'application/json');
res.json(readmsg_obj);
} else {
try {
readmsg_obj.message = '';
if ((dbQueryPromiseRead != null) && dbQueryPromiseRead.isPending()) {
logger.info(`Database request for Index-read is still pending!`);
return;
}
dbQueryPromiseRead = checkPendingRequest(getReadIndex());
dbQueryPromiseRead.then((message) => {
initialised_read = true;
readmsg_obj = {...message};
res.setHeader('Content-Type', 'application/json');
res.json(readmsg_obj);
}).catch((reject) => {
logger.error(`/../... : ${reject}`);
initialised_read = false;
res.status(500).send(reject);
});
} catch(err) {
logger.error(`/v1/... : ${err}`);
res.status(500).send(err);
};
}
});
You need to try add in node.js like global.dbCallState flag if operation is still running.
This global var one for all modules.
Do not change this object like global = new Object();, but you can use child field's.
https://nodejs.org/api/globals.html
You can change it in another module like global.dbCallState = false.
It not best solution, but it can help.
But i don't know, why you want only one connection. Its not good solution to block I/O in node.js
Related
I have this code and inside it I set the variable "showLoading" to true to show the user that the page is loading. Sometimes loading takes a long time to finish. How do I set the variable to false again when it finishes loading? Please give me a simple way since ive started coding javascript + angular weeks ago.
HTML inside the table:
<th scope="col" data-toggle="tooltip" title="Organize por data!"
class="table-pointer" ng-click='sortColumn("DATA_VENDA")'
ng-class='sortClass("DATA_VENDA")'>
Data
</th>
SCRIPT:
// column to sort
$scope.column = 'Data';
// sort ordering (Ascending or Descending). Set true for descending
$scope.reverse = false;
// called on header click
$scope.sortColumn = function(col){
$scope.showLoading = false;
$scope.column = col;
if($scope.reverse){
$scope.reverse = false;
$scope.reverseclass = 'arrow-up';
}else{
$scope.reverse = true;
$scope.reverseclass = 'arrow-down';
}
};
// remove and change class
$scope.sortClass = function(col){
if($scope.column == col){
if($scope.reverse){
return 'arrow-down';
}else{
return 'arrow-up';
}
}else{
return '';
}
}
Update
In another part of the code i did this, but i dont know how to apply for the function in the example..
$scope.enjuListBackPecafunction = function() {
$scope.mostrarLoading = true;
return $scope.enjuListBackPeca = Peca.query({
dateFromPeca:'20170101',
dateToPeca:'20180830'
// dateToPeca: $scope.endDate
}, function (err, values) {
$scope.mostrarLoading = false;
if (err) return err;
return values
});
};
Perhaps you want to have a Promise that controls the process that "sometimes takes a long time to finish". Let's imaging there is a method that implements asynchronous sorting:
var doSortAsync = function(column, order) {
return new Promise(function(resolve) {
// ... sending a sort request with column-order params
// ... getting the result asynchronously
// ... and calling the resolve method when it is done
resolve(result);
})
}
Then you just call it in your controller method with appropriate params:
$scope.sortColumn = function(col) {
$scope.showLoading = true;
$scope.column = col;
if($scope.reverse) {
$scope.reverse = false;
$scope.reverseclass = 'arrow-up';
} else {
$scope.reverse = true;
$scope.reverseclass = 'arrow-down';
}
doSortAsync(col, $scope.reverse).then(function(result) {
$scope.showLoading = false;
});
};
You need to know the exact moment when the process is done, and using Promise is a common approach here. In case you can't use ES6 Promises you may try AngularJS $q service.
Use the .finally method of the promise to stop the spinner:
$scope.showLoading = true;
$http.get(url)
.then(function(response) {
$scope.data = response.data;
}).catch(function(response) {
console.log(response.status);
throw response;
}).finally(function() {
$scope.showLoading = false;
});
The .finally block will always execute after the server responds either with the data or an error.
I am quessing that Peca.query is a third-party callback-based API. To integrate it with the AngularJS framework, use $q.defer:
function ajsPecaQuery(options) {
var deferred = $q.defer();
var resource = Peca.query(options, function (err, values) {
if (err) return deferred.reject(err);
//else
return resolve(values);
});
resource.$promise = deferred.promise;
return resource;
}
The function invokes the Peca.query operation and creates an AngularJS promise from the callback. It attaches the promise to the resource object with the property name $promise.
Then use it as so:
$scope.enjuListBackPecafunction = function() {
$scope.mostrarLoading = true;
var resource = ajsPecaQuery({
dateFromPeca:'20170101',
dateToPeca:'20180830'
// dateToPeca: $scope.endDate
});
resource.$promise.finally(function() {
$scope.mostrarLoading = false;
});
return $scope.enjuListBackPeca = resource;
};
The function starts the spinner at the beginning of the operation. It uses the .finally method of the promise to stop the spinner.
Don't forget to inject the $q service.
im here with an issue regarding react native, i started a project about 2 weeks ago and its the first time im trying this framework and never worked with javascript ever so its being hard...
What i would like to know is why my function called 'check_Authentication' wich just checks if the variables of a .js file called 'Globals" that have the default values presetted runs before i can finish my AsyncStorage tasks to asign it new ones
Here is my code
this two function as you can see are AsyncStorage and after i either get an item or set one...i call a funtion called 'Update_Globals' that is in another .js file and update my global variables from my other .js file called 'Globals' so that i can use them later on.
async function set_Async(field, value){
try {
await AsyncStorage.setItem(field, value);
await update_Globals(field,value);
} catch (error) {
throw new Error('something wrong with setting async storage');
}
}
async function get_Async(field){
try {
const value = await AsyncStorage.getItem(field);
await update_Globals(field,value);
} catch (error) {
throw new Error('something wrong with getting async storage');
}
}
Here is the .js file with the function to update my global variables mentioned above
import globals from './globals.js';
export function update_Globals(field,value){
alert("Updating field: " + field + " with value: " + value);
switch(field) {
case globals.is_logged_in_name:
globals.is_logged_in_value = value;
break;
case globals.account_status_name:
globals.account_status_value = value;
break;
case globals.account_role_name:
globals.account_role_value = value;
break;
case globals.account_user_name:
globals.account_user_value = value;
break;
case globals.account_api_token_name:
globals.account_api_token_value = value;
break;
case globals.account_profile_status_name:
globals.account_profile_status_value = value;
break;
default:
alert("No variable found")
}
}
and here is the .js file with the variables
module.exports = {
is_logged_in_name: 'is_logged_in',
is_logged_in_value: 'false',
account_status_name: 'account_status',
account_status_value: '0',
account_role_name: 'account_role',
account_role_value: '0',
account_user_name: 'account_user',
account_user_value: '0',
account_api_token_name: 'account_api_token',
account_api_token_value: '0',
account_profile_status_name: 'account_profile_status',
account_profile_status_value: 'empty',
testing_name: "name",
testing_value: "Chrystello",
};
and this function is where it all goes together
async function do_Login(){
return fetch(url, {
method: method,
headers: headers,
body: JSON.stringify({
email: email,
password: password,
firebase_token: firebase_token,
})
})
.then(function(responseData) {
if(responseData.ok){
return responseData.json();
}
throw new Error('Network response was not ok.');
})
.then(function(responseData) {
const response = responseData["response"];
const response_array = response[0];
set_Async(globals.account_user_name,
replace_Quote_Marks(JSON.stringify(response_array["user_id"]))).done();
set_Async(globals.account_status_name,
replace_Quote_Marks(JSON.stringify(response_array["status_id"]))).done();
set_Async(globals.account_profile_status_name,
replace_Quote_Marks(JSON.stringify(responseData["status"]))).done();
set_Async(globals.account_role_name,
replace_Quote_Marks(JSON.stringify(response_array["role_id"]))).done();
set_Async(globals.account_api_token_name,
replace_Quote_Marks(JSON.stringify(response_array["api_token"]))).done();
})
.then(function () {
try{
check_Authentication()
}catch(error){
throw new Error("couln't run function");
}
})
.catch(function(error) {
console.error(error);
});
}
in here you can see me calling the set_Async() function where i send the name and value i want to store and after it is suposed to update my .js file with the new values and after the 5 are done i then procede to checking each value to then decide wether the user can or can't log in but it always return the default number the first time i click the button, the second time works because he has the new values so i can only asume that the function check_Authenticantion is running before the AsyncStorages
the function check_aunthentication is this one
function check_Authentication() {
Alert.alert("I Got user " + globals.account_user_value)
if(globals.account_user_value != "0"){
const account_status = check_Account_Status(globals.account_status_value)
return_value = JSON.stringify(account_status["return_value"])
if(return_value == "true"){
screen = JSON.stringify(account_status["screen"]);
if(screen == "ForcePassword"){
return true;
}
const account_profile = check_Account_Profile(globals.account_profile_status_value)
return_value = JSON.stringify(account_profile["return_value"])
if(return_value == "true"){
screen = JSON.stringify(account_profile["screen"])
const account_role = check_Account_Role(globals.account_role_value)
return_value = JSON.stringify(account_role["return_value"])
if(return_value == "true"){
screen = JSON.stringify(account_role["screen"]) + screen
screen = replace_Quote_Marks(screen)
return true;
}else{
message = JSON.stringify(account_profile["message"])
Alert.alert(message)
return false;
}
}else{
message = JSON.stringify(account_profile["message"])
Alert.alert(message)
return false;
}
}else{
message = JSON.stringify(account_status["message"])
Alert.alert(message)
return false;
}
}else{
Alert.alert("No User Found")
return false;
}
}
and this is how i run the main function to trigger all the events
<TouchableHighlight
style = {styles.submit_button}
onPress={() => {
do_Login().done()
}
}>
<Text style = {styles.submit_text}>Login</Text>
</TouchableHighlight>
i have found a work arround, might not be the best option but id did the trick..
Removing the await from the AsyncStorage functions somehow made the check_Authentication wait for AsyncStorage to finish setting the values
thanks #SLaks for your time
I tried to configure backend factory to obtain data using Amazon Products Api.
Here is the script I execute in nodejs:
var amazon = require('amazon-product-api');
var client = amazon.createClient({
awsId: 'ID',
awsSecret: 'sectret',
awsTag: 'tag',
});
// SERVER
// var Promise = require('bluebird');
var koa = require('koa');
var router = require('koa-router')();
router.get('/node/:index', function* () {
this.body = yield client.browseNodeLookup({
browseNodeId: this.params.index,
});
});
router.get('/', function* (ctx, next) {
var node = yield client.browseNodeLookup({
browseNodeId: 1063498,
});
this.body = node;
});
var app = koa();
app.use(router.routes()).use(router.allowedMethods());
app.listen(8005);
At the frontend I use Promise.map() by bluebird.js to map an array of amazon's product nodes. At the final of the function, I expect to transform links (strings) in the Array to objects (obtained by API).
Here is the function:
someFn(links) { // links is an array of node IDs
return Promise.map(links, (link) => {
var link = link;
if (typeof link === "object" || level > 1) {
return link;
} else {
return loadUrl({
url: 'http://localhost:8005/node/'+link,
action: 'json',
}).then((res) => {
if (isJSON(res)) {
return new Category(res); // transform string to object
} else {
return link;
}
});
}
});
}
Amazon allows at max 10 queries, that's why I need to run the function a few times or loop it to obtain an object for the each string in the Array.
The idea is to wait for the successful answer at the backend or repeat the query (yield client.browseNodeLookup)
Or just pass the array of node IDs and get JSON of each as the result.
I have not a lot experience with nodejs server and routing configuration, so can you help me to configure it properly?
I still did not find the solution to use backend for the task, but I've updated a loadUrl function. The function runs itself until retrieved the successful answer:
function loadUrl(options) {
var options = options;
return new Promise((resolve, reject) => {
$.post('/', options).done((result) => {
if (result != 'Internal Server Error') {
resolve(result);
} else {
(function run(x) {
var x = x || 0;
// 300 - calls limit
// for me 100 is enough
if (x <= 300) {
setTimeout(function() {
$.post('/', options).done((result) => {
if (result != 'Internal Server Error') {
resolve(result);
} else {
console.log('call', x);
run(x+1);
}
});
}, 100);
} else {
resolve(result);
}
})();
}
});
});
}
As I understood, the $.post() has time limit for the answer, and thats why I've got the problem. The data is obtained by the backend factory, but my frontend script was not ready to wait for it and was stopped.
I am trying to write a polling method that polls a server periodically to check whether a zip file has already been created or not.
What I want to accomplish are the following:
Calls(ajax) an API that creates a zip file on server
Calls(ajax) another API that checks if the zip file has already been created (polling method)
Some subsequent process
Here is my code snippet ↓
var success: boolean = false;
//1. requests a server to create a zip file
this.apiRequest.downloadRequest(params,ApiUrl.URL_FOR_DOWNLOAD_REQUEST)
.then((resObj) => {
var apiRes: IDownloadService = resObj.data;
if (apiRes.status[0].statusCode == "000") {
success = true;
} else {
//Error
}
}).then(() => {
if (success) {
//2. polls the server to check if the zip file is ready
<- Polling method↓ ->
this.polling(params).then((zipUrl) => {
console.log(zipUrl); //always logs zipUrl
//some subsequent process...
});
}
});
Could anyone give some examples of polling method that would work in this case?
Added:
private polling(params: any): ng.IPromise<any> {
var poller = () => this.apiRequest.polling(params, ApiUrl.URL_FOR_POLLING);
var continuation = () => poller().then((resObj) => {
var apiRes: IDownloadService = resObj.data;
if (apiRes.zipFilePath == "") {
return this.$timeout(continuation, 1000);
} else {
return apiRes.zipFilePath;
}
})
var result: ng.IPromise<any> = continuation();
return result;
}
Basically abstract the methods out as shown below:
let poll = () => this.apiRequest.downloadRequest(params,ApiUrl.URL_FOR_DOWNLOAD_REQUEST)
let continuation = () => poll().then((/*something*/)=> {
/*if still bad*/ return continuation();
/*else */ return good;
})
continuation().then((/*definitely good*/));
Update
As requested in the comment below:
return this.$timeout(continuation, 1000);
This is needed to get angular to kick off a digest cycle.
I have a Node.js application that, upon initialisation, reads two tables from an SQL database and reconstructs their relationship in memory. They're used for synchronously looking up data that changes (very) infrequently.
Problem: Sometimes I can't access the data, even though the application reports successfully loading it.
Code:
constants.js
module.exports = {
ready: function () { return false; }
};
var log = sysLog('core', 'constants')
, Geo = require('../models/geo.js');
var _ready = false
, _countries = []
, _carriers = [];
function reload() {
_ready = false;
var index = Object.create(null);
return Geo.Country.find().map(function (country) {
var obj = country.toPlainObject()
, id = obj.id;
delete obj.id;
index[id] = obj;
return Object.freeze(obj);
}).then(function (countries) {
log.debug('Loaded ' + countries.length + ' countries');
_countries = countries;
return Geo.Carrier.Descriptor.find().map(function (carrier) {
var obj = carrier.toPlainObject();
if (obj.country) {
obj.country = index[obj.country];
}
return Object.freeze(obj);
}).then(function (carriers) {
log.debug('Loaded ' + carriers.length + ' carriers');
_carriers = carriers;
});
}).finally(function () {
_ready = true;
});
}
reload().catch(function (err) {
log.crit({ message: 'Could not load constants', reason: err });
process.exit(-42);
}).done();
module.exports = {
reload : reload,
ready : function () { return _ready; },
countries : function () { return _countries; },
carriers : function () { return _carriers; }
};
utils.js
var log = sysLog('core', 'utils')
, constants = require('./constants');
module.exports = {
getCountryByISO: function(iso) {
if (!iso) {
return;
}
if ('string' != typeof iso) {
throw new Error('getCountryByISO requires a string');
}
if (!constants.ready()) {
throw new UnavailableError('Try again in a few seconds');
}
switch (iso.length) {
case 2:
return _.findWhere(constants.countries(), { 'iso2' : iso.toUpperCase() });
case 3:
return _.findWhere(constants.countries(), { 'iso3' : iso.toUpperCase() });
default:
throw new Error('getCountryByISO requires a 2 or 3 letter ISO code');
}
},
getCarrierByCode: function(code) {
if (!code) {
return;
}
if ('string' != typeof code) {
throw new Error('getCarrierByCode requires a string');
}
if (!constants.ready()) {
throw new UnavailableError('Try again in a few seconds');
}
return _.findWhere(constants.carriers(), { 'code' : code });
},
getCarrierByHandle: function(handle) {
if (!handle) {
return;
}
if ('string' != typeof handle) {
throw new Error('getCarrierByHandle requires a string');
}
if (!constants.ready()) {
throw new UnavailableError('Try again in a few seconds');
}
return _.findWhere(constants.carriers(), { 'handle' : handle });
}
};
Use case
if (data.handle) {
carrier = utils.getCarrierByHandle(data.handle);
if (_.isEmpty(carrier)) {
throw new InternalError('Unknown carrier', { handle: data.handle });
}
}
What's going on: All errors are logged; as soon as I see an error (i.e. "Unknown carrier") in the logs, I check the SQL database to see if it should've been recognised. That has always been the case so far, so I check the debug log to see if data was loaded. I always see "Loaded X countries" and "Loaded Y carriers" with correct values and no sign of "Could not load constants" or any other kind of trouble.
This happens around 10% of the time I start the application and the problem persists (i.e. didn't seem to go away after 12+ hours) and seems to occur regardless of input, leading me to think that the data isn't referenced correctly.
Questions:
Is there something wrong in constants.js or am I doing something very obviously wrong? I've tried setting it up for cyclical loading (even though I am not aware of that happening in this case).
Why can't I (sometimes) access my data?
What can I do to figure out what's wrong?
Is there any way I can work around this? Is there anything else I could to achieve the desired behaviour? Hard-coding the data in constants.js is excluded.
Additional information:
constants.reload() is never actually called from outside of constants.js.
constants.js is required only in utils.js.
utils.js is required in app.js (application entry); all files required before it do not require it.
SQL access is done through an in-house library built on top of knex.js and bluebird; so far it's been very stable.
Versions:
Node.js v0.10.33
underscore 1.7.0
bluebird 2.3.11
knex 0.6.22
}).finally(function () {
_ready = true;
});
Code in a finally will always get called, regardless of if an error was thrown up the promise chain. Additionally, your reload().catch(/* ... */) clause will never be reached, because finally swallows the error.
Geo.Country.find() or Geo.Carrier.Descriptor.find() could throw an error, and _ready would still be set to true, and the problem of your countries and carriers not being set would persist.
This problem would not have occurred if you had designed your system without a ready call, as I described in my previous post. Hopefully this informs you that the issue here is really beyond finally swallowing a catch. The real issue is relying on side-effects; the modification of free variables results in brittle systems, especially when asynchrony is involved. I highly recommend against it.
Try this
var log = sysLog('core', 'constants');
var Geo = require('../models/geo.js');
var index;
var _countries;
var _carriers;
function reload() {
index = Object.create(null);
_countries = Geo.Country.find().map(function (country) {
var obj = country.toPlainObject();
var id = obj.id;
delete obj.id;
index[id] = obj;
return Object.freeze(obj);
});
_carriers = _countries.then(function(countries) {
return Geo.Carrier.Descriptor.find().map(function (carrier) {
var obj = carrier.toPlainObject();
if (obj.country) {
obj.country = index[obj.country];
}
return Object.freeze(obj);
});
});
return _carriers;
}
reload().done();
module.exports = {
reload : reload,
countries : function () { return _countries; },
carriers : function () { return _carriers; }
};
constants.reload() is never actually called from outside of
constants.js.
That's your issue. constants.reload() reads from a database, which is an aysnchronous process. Node's require() is a synchronous process. At the time constants.js is required in utils.js and the module.exports value is returned, your database query is still running. And at whatever point in time that app.js reaches the point where it calls a method from the utils module, that query could still be running, resulting in the error.
You could say that requiring utils.js has the side-effect of requiring constants.js, which has the side-effect of executing a database query, which has the side-effect of concurrently modifying the free variables _countries and _carriers.
Initialize _countries and _carriers as unresolved promises. Have reload() resolve them. Make the utils.js api async.
promises.js:
// ...
var Promise = require('bluebird');
var countriesResolve
, carriersResolve;
var _ready = false
, _countries = new Promise(function (resolve) {
countriesResolve = resolve;
})
, _carriers = new Promise(function (resolve) {
carriersResolve = resolve;
});
function reload() {
_ready = false;
var index = Object.create(null);
return Geo.Country.find().map(function (country) {
// ...
}).then(function (countries) {
log.debug('Loaded ' + countries.length + ' countries');
countriesResolve(countries);
return Geo.Carrier.Descriptor.find().map(function (carrier) {
// ...
}).then(function (carriers) {
log.debug('Loaded ' + carriers.length + ' carriers');
carriersResolve(carriers);
});
}).finally(function () {
_ready = true;
});
}
reload().catch(function (err) {
log.crit({ message: 'Could not load constants', reason: err });
process.exit(-42);
}).done();
module.exports = {
reload : reload,
ready : function () { return _ready; },
countries : function () { return _countries; },
carriers : function () { return _carriers; }
};
utils.js
getCarrierByHandle: function(handle) {
// ...
return constants.carriers().then(function (carriers) {
return _.findWhere(carriers, { 'handle' : handle });
});
}
Use case:
utils.getCarrierByHandle(data.handle).then(function (carrier) {
if (_.isEmpty(carrier)) {
throw new InternalError('Unknown carrier', { handle: data.handle });
}
}).then(function () {
// ... next step in application logic
});
This design will also eliminate the need for a ready method.
Alternatively, you could call constants.reload() on initialization and hang all possibly-dependent operations until it completes. This approach would also obsolete the ready method.
What can I do to figure out what's wrong?
You could have analyzed your logs and observed that "Loaded X countries" and "Loaded Y carriers" were sometimes written after "Unknown carrier", helping you realize that the success of utils.getCarrierByHandle() was a race condition.