Is it ok to have multiple consumers of 1 promise? - javascript

Sample code:
const googleLoadPromise = new Promise(function (resolve, reject) {
google.charts.setOnLoadCallback(function () {
resolve(1);
});
});
googleLoadPromise.then(function () {
// consumer 1 - do something
});
googleLoadPromise.then(function () {
// comsumer 2 - do something else
});
i.e. for the googleLoadPromise, there are two consumers. Is this sort of pattern ok? It seems to work ok - i.e. both consumers get called, and they don't seem to cause problems for each other.
Also, if this is ok, is the order of the running the consumers deterministic (just out of interest, more than anything)?

Yes of course.
You can also just do this if you want to run both functions in sequence:
const googleLoadPromise = new Promise(function (resolve, reject) {
google.charts.setOnLoadCallback(function () {
resolve(1);
});
});
googleLoadPromise
.then(function () {
// consumer 1 - do something
}).then(function () {
// comsumer 2 - do something else
});
Just also remember to handle rejections and catch your promise.
Promises become much easier with async await as well. The below example is more inline with what you wrote:
const googleLoadPromise = new Promise(function (resolve, reject) {
google.charts.setOnLoadCallback(function () {
resolve(1);
});
});
function1 = () => {
// consumer 1 - do something
}
function2 = () => {
// consumer 1 - do something
}
(async function() {
try {
const result = await googleLoadPromise();
function1();
function2();
}
catch( error ) {
console.error( error );
}
}());

Is this sort of pattern ok?
Sure, thats the main usecase of promises.
is the order of the running the consumers deterministic (just out of interest, more than anything)?
Yes, the callback attached first with then gets executed first, however I wouldnt rely on it. If you want to run one callback after another, chain them accordingly.
// ok:
promise
.then(first);
promise
.then(second);
// better if second depends on first:
promise
.then(first)
.then(second);

Related

Need to wait for promise result from top level code

I have to make an async call to a 3rd party API and I want the code execution to wait for the response.
async function getOrderData(orderId) {
return new Promise(function (resolve, reject) {
var service = new APIservice();
service.GetOrderData(oid, function (event) {
if (event && event.hasErrors() == false) {
resolve(event.result);
} else {
reject(null);
}
});
});
}
var order = getOrderData(orderId);
//from here the code should only resume once the var order is defined with whatever's returned from the api
This is the top level code (not async) so I cannot use await.
EDIT:
Answering some suggestions:
this.$onInit = function () {
this.order = "wrong value";
getOrderData(orderId).then(order => {
this.order = "correct value";
});
};
this function will end with "test" being "wrong value". This is what I am trying to avoid.
You can await the Promise returned by getOrderData().
However, in order to use await you need to wrap your call to getOrderData in an async function (there is no such thing as top-level await at the moment - it may one day be a thing however. At present, it can only be used when inside an async function):
// getOrderData function initializaion...
(async _ => {
var order = await getOrderData(orderId); // wait to recieve data before proceeding
// You can now write code wih `order`
})().catch(err => {
console.error(err);
});
Alternatively, you can write your code in a .then callback (which await is simply just syntactic sugar for) from the returned Promise of getOrderData() like so:
// getOrderData function initializaion...
getOrderData(orderId).then(order => {
// write code in here which relies on `order`
}).catch(err => {
console.error(err);
});;
you need something like this:
async function the_whole_thing() {
async function the_api_wrapper() {
return new Promise((resolve, reject) => {
setTimeout(() => resolve('joe!'), 10);
});
}
let result = await the_api_wrapper();
console.log(result);
}
the_whole_thing();
Note #1: I just swapped out the API you are calling with setTimeout, as it is also a function that takes a callback (similar to your case) and it also ensures stuff happen asynchronously.
Note #2: note that the async/await syntax can only be used in the body of another async function, so thats why I wrapped the whole thing, well, in the_whole_thing.

Promise all not firing

I'm trying to get promises work for the first time. However, my Promise.all is never firing.
I'm using node.js & express
const promises = [
new Promise( () => {
var query = `...`;
mssql.query(query, function(obj){
finalRes['key1'] = obj.recordset;
return true;
//this works
});
}),
new Promise( () => {
var query = `...`;
mssql.query(query, function(obj){
finalRes['key2'] = obj.recordset;
return true;
//this works
});
}),
...
]
Promise.all(promises).then(() => {
res.send(finalRes);
// this is never firing
});
I have been googling stuff and I can't find a solution. I would appreciate someone point out what I do wrong here.
Cheers
Your promise creation code is wrong, as they never resolve. What you actually should do is fire resolve functions inside your callback-based code. I'd go a bit further - and make all those promises resolve with their results instead of modifying some external value. Like this:
const promises = [
new Promise( (resolve, reject) => {
var query = `...`;
mssql.query(query, function(obj){
resolve({key1:obj.recordset});
});
}),
new Promise( (resolve, reject) => {
var query = `...`;
mssql.query(query, function(obj){
resolve({key2:obj.recordset});
});
}) // ...
];
Promise.all(promises).then(results => {
res.send(Object.assign({}, ...results));
});
Depending on how queries are built, you might go even further - and write a generic query-generator function, which takes query and key as params, and returns a promise. Again, this function should be easy to test.
As a sidenote, your code is overly optimistic, it should also provide error callbacks with reject() invoke to each query.

How to wait for a bluebird promise to settle in multiple locations?

I have a situation where a bunch of functions are needing to wait for a promise to settle because it's the init function;
self.init=new Promise(function(resolve){
//do stuff, take awhile
resolve();
});
But, while it's init'ing, the async nature means other functions that depend on it being init are being called. I want those functions to wait for the init to finish, then continue.
I tried doing this inside each function
function doSomethingUseful(){
self.init.reflect().then(function () {
//do functions purpose
});
}
function doSomethingUseless(){
self.init.reflect().then(function () {
//do functions purpose
});
}
But it only works randomly, probably only works if init has settled, and if it hasn't, it just hangs here, weirdly hangs the whole app, despite it being async.
I am trying to replace a former solution that involved intervals and checking a Boolean isInit in each function call.
Is there a bluebird function to do this? Or another way to keep waiting and checking on a promise to see if it is resolved?
The app has this sort of structure in a number of places. Usually around sqlite read/writes. An init to open the database, but while it's opening, the page is loading and it's already trying to read/write to the tables, so those read/writes are forced to wait by using setInterval and repeatedly checking to see if the init has finished.
Here's an example using google analytics.
function Analytics() {
var self = this;
self.ready = ko.observable(false).subscribeTo('application:ready'); //attached to page ready event in jquerymobile and cordova
self.trackerInit = new Promise(function (resolve, reject) {
ko.computed(function () {
if (self.ready()) {
window.ga.startTrackerWithId('id', 1000, resolve, reject);
}
});
});
}
Analytics.prototype.trackSpeed = function (cat, interval, variable, label) {
var self = this;
console.log("speed tracker", cat, interval, variable, label); //this logs
return self.trackerInit.then(function () {
console.log("speed tracker confirm init"); //this never logs, all execution stops including other async code
return new Promise(function (resolve, reject) {
window.ga.trackTiming(cat, interval, variable, label, resolve, reject);
});
}).catch(function (e) {
if (e.message === "send timeout") {
return true; //who cares about timeouts anyways
} else {
throw e;//rethrow it
}
});
};
Function is called within page change event without a return, purely async. Calling it causes all execution to stop.
The ready ko is done like this
self.ready = ko.observable(false).publishOn('application:ready');
var deviceReady = new Promise(function (resolve) {
$(document).on('deviceready', resolve);
});
var pageReady = new Promise(function (resolve) {
$(document).on('pagecreate', resolve);
});
Promise.all([deviceReady, pageReady]).then(function () {
//a couple of page of code and...
self.ready(true);
});
Changing the init like this produces the same result of a hang when checking it's results
self.trackerInit = new Promise(function (resolve, reject) {
console.log("initting");
checker = setInterval(function () {
if (window.ga) {
console.log("ready init");
window.ga.startTrackerWithId('id', 100, function(){
clearInterval(checker);
console.log("init complete");
resolve();
}, reject);
}
}, 1000);
});
They are just promises. Just use then to chain them
function doSomethingUseful() {
// wait for init to finish, then do our stuff
// return the new chained promise in case someone wants to wait on us
return self.init.then(function () {
// do stuff
});
}
function doSomethingUseless() {
// wait for init to finish, then do our stuff
// return the new chained promise in case someone wants to wait on us
return self.init.then(function () {
// do stuff
});
}
// do both of those things and then do something else!
Promise.all([doSomethingUseful(), doSomethingUseless()]).then(function () {
console.log("init is done. And we've done something useful and useless.")
}
Edit:
Based on your additional code, the problem is that if the application is "ready" before your Analytics component is constructed, then you will never receive the "application:ready" (because it came before you subscribed) so your "ready" observable will remain false. According to the postbox docs, you need to pass true as a second argument to subscribeTo so that you'll get the ready value even if it occurred in the past:
ko.observable(false).subscribeTo("application:ready", true)
However, constructing all of these observables and computeds just to feed into a promise is overkill. How about:
self.trackerInit = new Promise(function (resolve, reject) {
const s = ko.postbox.subscribe("application:ready", function (value) {
if (value) {
s.dispose(); // stop listening (prevent memory leak
window.ga.startTrackerWithId('id', 1000, resolve, reject);
}
}, true);
});
You can even turn this into a promise helper:
function whenReady(eventName) {
return new Promise((resolve, reject) => {
const s = ko.postbox.subscribe(eventName, value => {
if (ready) {
s.dispose();
resolve(value);
}
}, true);
});
}
function startGaTracker(id, timeout) {
return new Promise((resolve, reject) => window.ga.startTrackerWithId(id, timeout, resolve, reject);
}
Then you can write:
self.trackerInit = whenReady("application:ready")
.then(() => startGaTracker("id", 100));

Using Promises to call function inside another function with Bluebird promises library

I have 3 Node Js functions. What I'm trying to do here is, I want to call normalizeFilePath and get the normalized path, after that check whether a file exist or not with that normalizedFilePath and after all these, create a file if the file doesn't already exist. This is the first day of using promises (Bluebird) and I'm new to Node JS and Java Script. Below code structure is getting complex. Of course this is not a good idea at all.
var createProjectFolder = function (projectName) {
};
var checkFileExistance = function (filePath) {
return new promise(function (resolve, reject) {
normalizeFilePath(filePath).then(function (normalizedFilePath) {
return fs.existSync(normalizedFilePath);
});
})
};
var normalizeFilePath = function (filePath) {
return new promise(function (resolve, reject) {
resolve(path.normalize(filePath));
});
};
How can i manage promises to implement that concept?
Let's improve your code in two simple steps.
Promises are meant for async functions
As long as path.normalize is synchronous, it should not be wrapped in promise.
So it can be as simple as that.
var normalizeFilePath = function (filePath) {
return path.normalize(filePath);
};
But for now lets pretend that path.normalize is async, so we can use your version.
var normalizeFilePath = function (filePath) {
return new Promise(function (resolve, reject) {
resolve( path.normalize(filePath) );
});
};
Promisify all the things
Sync is bad. Sync blocks event loop. So, instead of fs.existsSync we will use fs.exists.
var checkFileExistance = function (filePath) {
return new Promise(function (resolve, reject) {
fs.exists(filePath, function (exists) {
resolve(exists);
});
});
};
As You can see, we are wrapping async function that accepts a callback with a promise. It's quite a common concept to "promisify" a function, so we could use a library for that. Or even use fs-promise, that is -- you guess it -- fs with promises.
Chaining promises
Now, what we want is making three actions one after another:
Normalize file path
Check if file already exists
If not, create a directory
Keeping that in mind, our main function can look like this.
var createProjectFolder = function (projectName) {
normalizeFilePath(projectName)
.then(checkFileExistance)
.then(function (exists) {
if (!exists) {
// create folder
}
})
.catch(function (error) {
// if there are any errors in promise chain
// we can catch them in one place, yay!
});
};
Don't forget to add the catch call so you would not miss any errors.

Chaining promises with RxJS

I'm new to RxJS and FRP in general. I had the idea of converting an existing promise chain in my ExpressJS application to be an observable for practice. I am aware that this probably isn't the best example but maybe someone can help shed some light.
What I'm trying to do:
I have two promises - prom1 and prom2
I want prom1 to run before prom2
If prom1 sends a reject(err), I want to cancel prom2 before it starts.
I want the error message prom1 returns to be available to the onError method on the observer.
var prom1 = new Promise(function(resolve, reject) {
if (true) {
reject('reason');
}
resolve(true);
});
var prom2 = new Promise(function(resolve, reject) {
resolve(true);
});
// What do I do here? This is what I've tried so far...
var source1 = Rx.Observable.fromPromise(prom1);
var source2 = source1.flatMap(Rx.Observable.fromPromise(prom2));
var subscription = source2.subscribe(
function (result) { console.log('Next: ' + result); },
// I want my error 'reason' to be made available here
function (err) { console.log('Error: ' + err); },
function () { console.log('Completed'); });
If I understood what you are trying to do - you need to create two deferred observables from functions that return promises and concat them:
var shouldFail = false;
function action1() {
return new Promise(function (resolve, reject) {
console.log('start action1');
if (shouldFail) {
reject('reason');
}
resolve(true);
});
}
function action2() {
return new Promise(function (resolve, reject) {
console.log('start action2');
resolve(true);
});
}
var source1 = Rx.Observable.defer(action1);
var source2 = Rx.Observable.defer(action2);
var combination = Rx.Observable.concat(source1, source2);
var logObserver = Rx.Observer.create(
function (result) {
console.log('Next: ' + result);
},
function (err) {
console.log('Error: ' + err);
},
function () {
console.log('Completed');
});
then for normal case:
combination.subscribe(logObserver);
// start action1
// Next: true
// start action2
// Next: true
// Completed
And case where fisrt promise fails:
shouldFail = true;
combination.subscribe(logObserver);
// start action1
// Error: reason
http://jsfiddle.net/cL37tgva/
flatMap turns an Observable of Observables into an Observable. It's used in many examples with Promises because often you have an observable and in the map function you want to create a promise for each "item" the observable emmits. Because every fromPromise call creates a new Observable, that makes it an "observable of observables". flatMap reduces that to a "flat" observable.
In your example you do something different, you turn a single promise into an observable and want to chain it with another observable (also created form a single promise). Concat does what you are looking for, it chains two observables together.
The error case will work as you would expect.
Observable.forkJoin works great here receiving array of other Observables.
Rx.Observable.forkJoin([this.http.get('http://jsonplaceholder.typicode.com/posts'), this.http.get('http://jsonplaceholder.typicode.com/albums')]).subscribe((data) => {
console.log(data);
});

Categories

Resources