I am trying to reuse the the data returned from promise here. But, the problem is, after the first call to checkPromise function, it immediately calls the second function, and the promise for the first function is not fulfilled, so it never returns any data, and hence it never enters the if clause. How do I reuse a promise?
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
var url = 'http://www.google.com';
var obj = new Object;
function apiCall(url) {
return new Promise(function (resolve, reject) {
request(url).spread(function(response, body) {
return resolve(body);
}).catch(function(err) {
console.error(err);
return reject(err);
});
});
}
function checkPromise(url) {
if(obj.hasOwnProperty(url)) {
var rp = obj[url];
//do something
}
else {
apiCall(url).then(function(result) {
obj[url] = result;
//do something
});
}
}
checkPromise(url);
checkPromise(url);
You likely have a timing issue. Your apiCall() function is asynchronous. That means it finishes sometime later. As such, each time you call checkPromise(), all you're doing is starting a request and it finishes sometime later. So, you call it the first time and it starts a request (that has not finished yet). Then, your next call to checkPromise() gets called and it does it's if check before the first call has completed. Thus, it finds nothing in the cache yet.
Your code is running two requests in parallel, not one after the other.
If you actually want to wait until the first request is done before executing the second one, then you will have to actually structure your code to do that. You would need to make checkPromise() return a promise itself so code using it could known when it was actually done in order to execute something after it was done.
FYI, I don't see anything in your code that is actually related to reusing promises (which is something you cannot do because they are one-shot objects).
Here's one possible implementation:
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
var url = 'http://www.google.com';
var obj = {};
function apiCall(url) {
return request(url).spread(function(response, body) {
return body;
});
}
function checkPromise(url) {
if(obj.hasOwnProperty(url)) {
var rp = obj[url];
//do something
return Promise.resolve(rp);
}
else {
return apiCall(url).then(function(result) {
obj[url] = result;
//do something
return result;
});
}
}
checkPromise(url).then(function() {
checkPromise(url);
});
Significant changes:
Return the promise returned by request() rather than create yet another one.
Change checkPromise() so it always returns a promise whether the value is found in the cache or not so calling code can always work consistently.
Sequence the two checkPromise() calls so the first can finish before the second is executed.
A very different approach would be to actually wait on the cache if a result you are interested in is already being loaded. That could be done like this:
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
var url = 'http://www.google.com';
var obj = {};
function apiCall(url) {
return request(url).spread(function(response, body) {
return body;
});
}
function checkPromise(url) {
if(obj.hasOwnProperty(url)) {
// If it's a promise object in the cache, then loading
// If it's a value, then the value is already available
// Either way, we wrap it in a promise and return that
return Promise.resolve(obj[url]);
} else {
var p = apiCall(url).then(function(result) {
obj[url] = result;
//do something
return result;
});
obj[url] = p;
return p;
}
}
checkPromise(url).then(function(result) {
// use result
});
checkPromise(url).then(function(result) {
// use result
});
few problems with your code, first in apiCall, you are doing a promise ant-pattern( no need for that new promise), second your checkPromise is doing a sync operation, so it must either return a promise or have a callback argument, so you code can be changed into:
var Promise = require('bluebird');
var request = Promise.promisify(require("request"));
var url = 'http://www.google.com';
var obj = new Object;
function apiCall(url) {
return request(url).spread(function(response, body) {
return body;
}).catch(function(err) {
console.error(err);
throw err;
});
}
function checkPromise(url) {
var promise = Promise.resolve();
if(obj.hasOwnProperty(url)) {
var rp = obj[url];
//do something
}
else {
return apiCall(url).then(function(result) {
obj[url] = result;
//do something
});
}
return promise;
}
checkPromise(url).then(function(){
return checkPromise(url);
});
Given the way you are globally storing the result in 'obj[url]', it'd probably be easiest to do
function checkPromise(url) {
if (!obj[url]) obj[url] = apiCall(url);
obj[url].then(function(result) {
//do something
});
}
to basically make the request, if it hasn't already started, then attach a listener to the promise for when the result has loaded.
Here is the simplest example of how to prevent multiple API calls if there are multiple similar request for something (cache check for example)
var _cache = {
state: 0,
result: undefined,
getData: function(){
log('state: ' + this.state);
if(this.state === 0 ){ // not started
this.state = 1; // pending
this.promise = new Promise(function(resolve, reject) {
return (apiCall().then(data => { _cache.result = data; _cache.state = 2; resolve(_cache.result) }));
})
return this.promise;
}
else if(this.state === 1){ // pending
return this.promise;
}
else if(this.state === 2){// resolved
return Promise.resolve(this.result);
}
},
};
Simulating api call
function apiCall(){
return new Promise(function(resolve, reject) {
log('in promise')
setTimeout(() => {
log('promise resolving')
resolve(1);
}, 1000);
})
}
Making simultaneous requests.
_cache.getData().then(result => { log('first call outer: ' + result);
_cache.getData().then(result => { log('first call inner: ' + result); });
});
_cache.getData().then(result => { log('second call outer: ' + result);
_cache.getData().then(result => { log('second call inner: ' + result); });
});
Only one API call is maden. All others will wait for completion or use the resolved result if it already completed.
Related
I've a bunch of functions which are nested due to top level function is a ajax request.
So i want to return a value instead of a promise in nested child function.
Parent
let getUserPermissions = function(id) {
let deferred = $q.defer();
let promise = accessRequestService.getPermissions(id);
promise.then(function(data) {
deferred.resolve(data);
}, function(err) {
deferred.reject(err);
})
return deferred.promise;
}
Child 1
$rootScope.userInit = function() {
return getUserPermissions(vzid)
.then(function(data) {
//Some code here
return data;
})
}
Child 2
let checkAuthorize = function(toState) {
return $rootScope.userInit().then(
function(data) {
//some code here
return data;
});
}
Level 3
checkAuthorize(toState).then( function(val){
$rootScope.isAuthorized = val;
if ($rootScope.isAuthorized == true) {
$log.info('is Authorized')
} else {
$log.info('is not Authorized');
throw new AuthorizationError()
}
})
At Level 3 we are still working with a promise. Can child 2 return a value instead of promise.
Expectation # Level 3
$rootScope.isAuthorized = checkAuthorize(toState);
if ($rootScope.isAuthorized == true) {
$log.info('is Authorized')
} else {
$log.info('is not Authorized');
throw new AuthorizationError()
}
The hard truth is: you can't, unless you want spaghetti code all around.
The best solution would be to use something like ui-router's resolve, getting all the permissions needed before the page is shown to the user. Then, you could use them on your controllers without any asynchronous calls.
You can use for it async/await construction. And use Babel for support old browsers.
Async
Await
function resolveAfter2Seconds(x) {
return new Promise(resolve => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}
async function f1() {
var x = await resolveAfter2Seconds(10);
console.log(x); // 10
console.log('done');
}
f1();
Yes, this type of thing is possible, but it will change the behavior. You'll probably want to keep userInit, but you also add a userInitValue variable and initialize it as follows:
let userInitValue = null;
let userInit = function() {
return getUserPermissions()
.then(function(data) {
userInitValue = data;
return data;
})
}
So now userInitValue will start as null and then later be initialized to the relevant data.
function isKnownAuthorized(toDoSomething) {
// If we don't know whether the user is authorized
// because we are still waiting for the server to tell us
// then return false and disallow access for now
if(!userInitValue) return false;
// Otherwise return the truth
// (as of when we got the server response)
return userInitValue.isAuthorized(toDoSomething);
}
Note again the change in behavior. The price of getting an instant response, perhaps before the server gives you the data, is that the instant response could be wrong. So don't use this in a one-time :: expression in AngularJs.
Based on what you're hoping to achieve in Level 3, I'm guessing this function is going to be called multiple times with the same input. In this case, what I would do is make the call to the promise if there is not a cached result, and cache the result. This way you don't have to go down the promise chain, although I only count one promise in the code provided. There are multiple handlers on resolve, but only one promise.
You can run your code as if it was synchronous using nsynjs: it will evaluate code step-by-step, and if some function returns promise, it will pause execution, wait until promise is resolved, and assigns resolve result to data property. So, code below will be paused on level 1 until promise is resolved to actual value.
var getUserPermissions = function(id) {
return new Promise(function(resolve, reject) {
setTimeout(function(){
resolve({
id: id,
isAdmin: "yes he is",
})
}, 1000);
});
};
function synchronousCode() {
console.log("start");
var vzid = 35;
var userInit = function() {
return getUserPermissions(vzid).data;
};
var checkAuthorize = function() {
return userInit().isAdmin;
};
var isAuthorized = checkAuthorize();
console.log(isAuthorized);
};
nsynjs.run(synchronousCode, null, function(){
console.log("finish");
});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>
I'm using $state.transitionTo Method to be called before $stateChangeStart.
var transitionTo = $state.transitionTo;
$state.transitionTo = function(to, toParams, options) {
var from = $state.$current,
fromParams = $state.params;
to = to.name ? to : $state.get(to);
$rootScope.state = {
to: to.self,
toParams: toParams,
from: from.self,
fromParams: fromParams,
options: options
}
if (options.notify && options.notify !== false) {
return $q.reject(new AuthorizationError('Rejecting $state.transitionTo', 'Transition Rejected'));
} else {
return checkAuthorize(to).then(function(auth) {
$rootScope.isAuthorized = auth;
return transitionTo(to, toParams, options)
})
}
}
StateChangeStart
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
$log.info("Route change start from", fromState.url, "to", toState.url);
//event.preventDefault();
if ($rootScope.isAuthorized == true) {
$log.info('is Authorized')
//$state.go($rootScope.toState.name);
} else {
event.preventDefault();
$log.info('is not Authorized');
throw new AuthorizationError('User is not Authorized.', 'NOT_AUTHENTICATED')
}
});
I have the following discovery code using the mdns-js package.
in ./lib/deviceDiscovery.js:
var mdns = require('mdns-js');
const browsers = new Map();
const INFINITE = -1;
function createABrowser(theServiceType, timeout) {
if (browsers.has(theServiceType)) {
return;
}
return new Promise(function(resolve, reject) {
var browser = mdns.createBrowser(theServiceType);
browser.on('ready', function() {
browsers.set(theServiceType, browser);
resolve(browser);
});
if (timeout != INFINITE) {
setTimeout(function onTimeout() {
try {
browser.stop();
browsers.delete(browser.serviceType);
} finally {
reject('browser ' + browser.toString() + ' timed out.');
}
}, timeout);
}
});
}
module.exports.startService = function(services, timeout) {
timeout = timeout || INFINITE;
promises = [];
services.forEach(function(service) {
promises.push(createABrowser(service, timeout));
});
return Promise.all(promises);
}
module.exports.stopService = function() {
browsers.values().forEach(function(browser) {
browser.stop();
});
browsers.clear();
}
module.exports.getDevices = function() {
if (browsers.size == 0) {
reject('service was stopped');
} else {
const promises = [];
for (let browser of browsers.values()) {
promises.push(new Promise(function(resolve, reject) {
try {
browser.discover();
browser.on('update', function(data) {
mfps = new Set();
const theAddresses = data.addresses;
theAddresses.forEach(function(element) {
mfps.add(element);
});
resolve(mfps);
});
} catch(err) {
reject(err);
}
}));
};
return Promise.all(promises).then(function(values) {
return new Set(values);
}, function(reason) {
return reason;
});
}
}
and use it in another file like this:
const DeviceDiscoveryService = require('./lib/deviceDiscovery');
var co = require('co');
co(function *service() {
yield DeviceDiscoveryService.startService([internetPrinter, pdlPrinter, unixPrinter], TIMEOUT);
yield DeviceDiscoveryService.getDevices();
}).catch(onerror);
function onerror(err) {
// log any uncaught errors
}
The problem is that the second yield hangs; it seems that the promise returned by getDevices function isn't resolved indefinitely, although I see that the individual promises are resolved.
startService uses a similar Promise.all(...) but it works ok.
Another related question is about the mdns-js: it seems that for each (input) service, the browser receives multiple updates.
But I resolve the promise for each browser after the first update event... do I need to wait for multiple updates and how?
Any hints will be greatly appreciated! Thanks.
I believe that you share update be returning a promises from createABrowser at ALL times (instead of returning undefined if the service already exists). Without returning a promise, I think Promise.all() won't resolve.
Instead, create a promise at the top and resolve if it the service exists already, and return THAT promise.
For the getDevices() call, you're running a reject without returning a promise there as well. Would this work?
module.exports.getDevices = function() {
if (browsers.size == 0) {
// Create a new promise, return it, and immediately reject
return new Promise(function(resolve, reject) { reject('service was stopped') };
// reject('service was stopped'); <- There wasn't a promise here
} else {
const promises = [];
for (let browser of browsers.values()) {
promises.push(new Promise(function(resolve, reject) {
try {
browser.discover();
browser.on('update', function(data) {
mfps = new Set();
const theAddresses = data.addresses;
theAddresses.forEach(function(element) {
mfps.add(element);
});
resolve(mfps);
});
} catch(err) {
reject(err);
}
}));
};
return Promise.all(promises).then(function(values) {
return new Set(values);
}, function(reason) {
return reason;
});
}
}
const dbConnection = require("../dbConnection");
var task = function () {
var response = "";
dbConnection.then(function () {
//do something here
response = "some value";
})
.catch(function () {
response = new Error("could not connect to DB");
});
//I can't return response here because the promise is not yet resolved/rejected.
}
I'm using a node module that someone else wrote. It returns a promise. I want to return either a string or a new Error() depending on whether the Promise object returned by the module resolved or not. How can I do this?
I can't return inside the finally() callback either because that return would apply to the callback function not my task function.
dbConnection.then().catch() will itself return a promise. With that in mind, we can simply write the code as return dbConnection.then() and have the code that uses the function treat the return value as a promise. For example,
var task = function () {
return dbConnection.then(function() {
return "Good thing!"
}).catch(function() {
return new Error("Bad thing.")
})
}
task().then(function(result){
// Operate on the result
}
const dbConnection = require("../dbConnection");
var task = function () {
var response = "";
return dbConnection.then(function () {
//do something here
response = "some value";
return response;
})
.catch(function () {
response = new Error("could not connect to DB");
return response;
});
}
This will return a promise which you can then chain.
The point of using promises is similar to using callbacks. You don't want the CPU to sit there waiting for a response.
Imagine i have a simple javascript function:
function someFunction(integer)
{
data = integer + 1;
return data;
}
I need to call this from inside another function and use the returned value:
function anotherFunction(integer)
{
int_plus_one = someFunction(integer);
//Do something with the returned data...
int_plus_two = int_plus_one + 1;
return int_plus_two;
}
How can i ensure that the return of anotherFunction return is only returned after someFunction completes? It actually seems to work ok with very fast functions like these. However if someFunction has to do some ajax lookups, the return of aotherFunction fails.
Thanks,
Steve
You do not know when or even if an asynchronous function will complete. The only way to handle this is to use a callback function, a function that gets executed after the async operation has completed.
This was my "aha!" moment: How to return the response from an asynchronous call?
As far as your code is sync, the approach above is fine.
Once you start introducing async parts, the one below involving callbacks is a common used approach:
function fn (v, cb) {
doSomethingAsyncWithV(function (err, _v) {
if(err) return cb(err);
cb(null, _v);
})
}
function yourFirstFn () {
var v = 0;
fn(v, function (err, _v) {
// do here whatever you want with the asynchronously computed value
});
}
How about promise? With that in mind, there's no need to worry about callback. It's one of the cool things in AngularJS.
var q = require('q');
var myPromise =function() {
var deferred = q.defer();
setTimeout(function(){
var output = anotherFunction(1);
deferred.resolve(output)
}, 10000); // take times to compute!!!
return deferred.promise;
}
var objPromise = myPromise();
objPromise.then(function(outputVal){
console.log(outputVal) ; // your output value from anotherFunction
}).catch(function(reason) {
console.log('Error: ' + reason);
})
then is ONLY exeucted after promise has been resolved. If an exception or error is caught, the catch function executes.
how about this?
function someFunction(integer, callback)
{
data = integer + 1;
return callback(data);
}
function anotherFunction(integer)
{
int_plus_one = someFunction(integer, function(data){
int_plus_two = int_plus_one + 1;
return int_plus_two;
});
//Do something with the returned data...
}
You could use promises:
new Promise(function someFunction(resolve, reject) {
ajaxLib.get(url, options, function (data) {
resolve(data);
});
}).then(function anotherFunction(integer)
{
int_plus_one = integer;
//Do something with the returned data...
int_plus_two = int_plus_one + 1;
return int_plus_two;
});
If you use jQuery, $.ajax returns a thenable :
$.ajax(url, options).then(function processDataFromXHR(data) {
return data.integer;
}).then(function anotherFunction(integer){
int_plus_one = integer;
//Do something with the returned data...
int_plus_two = int_plus_one + 1;
return int_plus_two;
});
I got a question regarding the solr-client module of nodejs. I'm using this module for querying against a solr-index.
The module itself works fine as long as I don't have to wait for finishing of the query and as long I need the result only as a async result.
But currently I cannot find out, how I will be able to await the finishing of a search request and use the result in a sequential way.
I have the follwing method in my manager
SolrManager.prototype.promisedQuery = function(query, callback) {
var solrClient = solr.createClient(this.configuration.cores.page);
var docs = null;
var finished = false;
var deferred = Q.defer();
var request = solrClient.search(query, function(err,obj){
if (!err) {
if (obj.response.numFound > 0) {
deferred.resolve(obj.response.docs);
} else {
deferred.resolve(null);
}
} else {
deferred.reject(err);
}
});
var records = null;
var promise = deferred.promise;
promise.then(function(result) {
records = result;
}).fail(function(error){
records = error;
});
return records;
};
The problem here is, that I try to wait for the result of the query and use it as return value of "promisedQuery".
I try since days to use this method in a sequential call, also with different additional modules like "wait.for", "q", etc. but nothing seems to work.
The callback function of the solr-client will always be executed after the manager-method has already returned. Also the promise-methods will be even called after the return from the manager-method.
Can someone help me out on that topic or have some tips, how I can await the response of the solr-client-search operation and then give it back in a sequential way?
Thanks for any help.
Udo Gerhards
over one week, it seems now that I have found a solution:
SolrManager.prototype.promisedQuery = function(query, callback) {
var solrClient = solr.createClient(this.configuration.cores.page);
var docs = null;
var deferred = Q.defer();
var request = solrClient.search(query, function(err,obj){
if (!err) {
if (obj.response.numFound > 0) {
deferred.resolve(obj.response.docs);
} else {
deferred.resolve(null);
}
} else {
deferred.reject(err);
}
});
return deferred.promise;
};
in all other managers, which are calling the above function:
...
var dbPromise = this.solrManager.promisedQuery(query);
var _self = this;
return Q.async(function*(){
var result = yield dbPromise;
return result;
});
...
After first tests, it seems that synchronized methods will wait until the promise is settled.
The only thing is, that it runs only with NodeJs version 0.11.10, which supports generator functions, with activated --harmony-flag and "q"-module.
Best regards
Udo
You are just using the promises a bit incorrectly. Instead of returning records, you need to return 'deferred.promise'. It should look something like this (note that you don't need the callback you passed into promisedQuery).
SolrManager.prototype.promisedQuery = function(query) {
var solrClient = solr.createClient(this.configuration.cores.page),
deferred = Q.defer();
solrClient.search(query, function(err,obj){
if (!err) {
if (obj.response.numFound > 0) {
deferred.resolve(obj.response.docs);
} else {
deferred.resolve(null);
}
} else {
deferred.reject(err);
}
});
return deferred.promise;
};
To use it you would do something like:
SolrManager.promisedQuery(myquery)
.then(function (data) {
// data is whatever your 'resolved' in promisedQuery
}, function (err) {
// err is whatever you rejected in promisedQuery
});
based on rquinns answer I've changed the code like follows:
SolrManager.prototype.promisedQuery = function(query, callback) {
var solrClient = solr.createClient(this.configuration.cores.page);
var docs = null;
var finished = false;
var deferred = Q.defer();
var request = solrClient.search(query, function(err,obj){
if (!err) {
if (obj.response.numFound > 0) {
deferred.resolve(obj.response.docs);
} else {
deferred.resolve(null);
}
} else {
deferred.reject(err);
}
});
return deferred.promise;
};
...
DemoObject.prototype.toString = function() {
return SolrManager.promisedQuery(this.query).then(function(result){
return result['title'];
}).fail(function(error){
return error;
});
};
DemoObject.prototype.typeOf = function() {
return SolrManager.promisedQuery(this.query).then(function(result){
return result['title'];
}).fail(function(error){
return error;
});
};
I think, this is the right way to use the "promise"-object. But what happens when i do the follwing:
...
var demoObject = new DemoObject();
demoObject.query = "id:1";
console.log(''+demoObject);
...
or if I use "demoObject" by concatenating it to a string
...
var string = "Some string "+demoObject;
...
In case of the string concatenation, I'm currently not sure that the string will contain also the title field from the database. Same for console output.
Will nodejs be so intelligent that it resolves for e.g. the string concatenation "after" the results from the database will be available?
BR
Udo