Combining two promises - javascript

I am really new to JavaScript and promises and to be honest I don't fully understand how promises work so I need some help.
I am using Google Cloud Messaging to push notifications from my site to my users. When users receive a notification and clicks on it, it opens a URL stored in a IndexedDB.
importScripts('IndexDBWrapper.js');
var KEY_VALUE_STORE_NAME = 'key-value-store', idb;
function getIdb() {
if (!idb) {
idb = new IndexDBWrapper(KEY_VALUE_STORE_NAME, 1, function (db) {
db.createObjectStore(KEY_VALUE_STORE_NAME);
});
}
return idb;
}
self.addEventListener('notificationclick', function (event) {
console.log('On notification click: ', event);
event.notification.close();
event.waitUntil(getIdb().get(KEY_VALUE_STORE_NAME, event.notification.tag).then(function (url) {
var redirectUrl = '/';
if (url) redirectUrl = url;
return clients.openWindow(redirectUrl);
}));
});
So in the code above, I know that the getIdb()...then() is a promise, but is the event.waitUntil also a promise?
The problem with the above code is that it opens a instance of Chrome every time the notification is clicked and I would prefer that it would utilize an existing instance if available. The following does just that:
self.addEventListener('notificationclick', function(event) {
console.log('On notification click: ', event.notification.tag);
event.notification.close();
event.waitUntil(
clients.matchAll({
type: "window"
})
.then(function(clientList) {
for (var i = 0; i < clientList.length; i++) {
var client = clientList[i];
if (client.url == '/' && 'focus' in client)
return client.focus();
}
if (clients.openWindow) {
return clients.openWindow('/');
}
})
);
});
However, now I have two promises, getIdb and clients.matchAll and I really have no idea how to combine the two promises and the two sets of code. Any help would be greatly appreciated. Thanks!
For reference, here is IndexDBWrapper.js:
'use strict';
function promisifyRequest(obj) {
return new Promise(function(resolve, reject) {
function onsuccess(event) {
resolve(obj.result);
unlisten();
}
function onerror(event) {
reject(obj.error);
unlisten();
}
function unlisten() {
obj.removeEventListener('complete', onsuccess);
obj.removeEventListener('success', onsuccess);
obj.removeEventListener('error', onerror);
obj.removeEventListener('abort', onerror);
}
obj.addEventListener('complete', onsuccess);
obj.addEventListener('success', onsuccess);
obj.addEventListener('error', onerror);
obj.addEventListener('abort', onerror);
});
}
function IndexDBWrapper(name, version, upgradeCallback) {
var request = indexedDB.open(name, version);
this.ready = promisifyRequest(request);
request.onupgradeneeded = function(event) {
upgradeCallback(request.result, event.oldVersion);
};
}
IndexDBWrapper.supported = 'indexedDB' in self;
var IndexDBWrapperProto = IndexDBWrapper.prototype;
IndexDBWrapperProto.transaction = function(stores, modeOrCallback, callback) {
return this.ready.then(function(db) {
var mode = 'readonly';
if (modeOrCallback.apply) {
callback = modeOrCallback;
}
else if (modeOrCallback) {
mode = modeOrCallback;
}
var tx = db.transaction(stores, mode);
var val = callback(tx, db);
var promise = promisifyRequest(tx);
var readPromise;
if (!val) {
return promise;
}
if (val[0] && 'result' in val[0]) {
readPromise = Promise.all(val.map(promisifyRequest));
}
else {
readPromise = promisifyRequest(val);
}
return promise.then(function() {
return readPromise;
});
});
};
IndexDBWrapperProto.get = function(store, key) {
return this.transaction(store, function(tx) {
return tx.objectStore(store).get(key);
});
};
IndexDBWrapperProto.put = function(store, key, value) {
return this.transaction(store, 'readwrite', function(tx) {
tx.objectStore(store).put(value, key);
});
};
IndexDBWrapperProto.delete = function(store, key) {
return this.transaction(store, 'readwrite', function(tx) {
tx.objectStore(store).delete(key);
});
};

One way to deal with multiple promises is with Promise.all
Promise.all([promise0, promise1, promise2]).then(function(valArray) {
// valArray[0] is result of promise0
// valArray[1] is result of promise1
// valArray[2] is result of promise2
});
read about promise.all - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

event.waitUntil() takes a promise - this allows the browser to keep your worker alive until you've finished what you want to do (i.e. until the promise that you gave to event.waitUntil() has resolved).
As the other answer suggests, you can use Promise.all() within event.waitUntil. Promise.all() takes an array of promises and returns a promise, so you can call then on it. Your handling function will get an array of promise results when all of the promises you've provided to Promise.all have resolved. Your code will then look something like this (I haven't actually tested this, but it should be close):
self.addEventListener('notificationclick', function (event) {
event.notification.close();
event.waitUntil(Promise.all([
getIdb().get(KEY_VALUE_STORE_NAME, event.notification.tag),
clients.matchAll({ type: "window" })
]).then(function (resultArray) {
var url = resultArray[0] || "/";
var clientList = resultArray[1];
for (var i = 0; i < clientList.length; i++) {
var client = clientList[i];
if (client.url == '/' && 'focus' in client)
return client.focus();
}
if (clients.openWindow) {
return clients.openWindow(url);
}
}));
});

Though I am very late to the party but,
I think you can use this very light, vanilla JS library that does things for you. https://www.npmjs.com/package/easy-promise-all
Here is the small code sample for it.
var { EasyPromiseAll } = require('easy-promise-all');
EasyPromiseAll({
resultFromPromise1: Promise.resolve('first'),
resultFromPromise2: Promise.reject('second'),
}).then((results) => {
const { resultFromPromise1, resultFromPromise2 } = results;
console.log(resultFromPromise1, resultFromPromise2);
});

Related

Compiling a result through a chain of promises (JS inheritance/interfaces)

I'm building a "storage provider" that allows consuming code to store stuff through an interface. Consider the below code snippets to be pseudocode as I'm going for MCVE. I'm trying to get my hands on IMPORTANTDATA and IMPORTANTKEY below.
At the lowest level, I have a baseService:
define([], function(){
return function(){
this.sendRequest = function(data){
return $.ajax(data).done(function(response){
return response.IMPORTANTDATA; // <---- This is needed
}).fail(function(response){
throw new Error(response);
});
}
}
})
I build services with this to reuse some base functionality, for example - eventService:
define(["baseService"], function(baseService){
const eventService = new baseService();
eventService.postMediaEvent = function(eventType, mediaPath, storageProvider){
// isolated logic here
return eventService.sendRequest(someData);
}
})
This is where things start to get tricky: I have a baseStorageClient:
define(["eventService"], function (eventService) {
return function(){
this.storageProvider = null;
const self = this;
this.storeMetadata = function(eventType, mediaPath){
return eventService.postMediaEvent(eventType, mediaPath, self.storageProvider);
};
this.storeMedia = function(){
throw new Error("Not Implemented");
};
}
}
But this guy isn't ever used directly. I have instances of this created - for example, indexedDbClient:
define(["baseStorageClient"], function(baseStorageClient){
const indexedDbClient = new baseStorageClient();
indexedDbClient.storeMedia = function(blob){
return openDatabase().then(function () {
const request = database.transaction(storeName, "readwrite")
.objectStore(storeName)
.add(dbEntry);
request.onsuccess = function (event) {
logger.log("combined segments saved into database.");
// todo - figure out how to resolve here
return {
IMPORTANTKEY: dbEntry.mediaId // <---- This too
}
};
request.onerror = function (event) {
// todo: figure out how to reject here
logger.log("Unable to save segments " + e);
};
});
}
})
And this client is used within my storageInterface:
define(["indexedDbClient"], function(indexedDbClient){
const storageInterface = {};
var currentClient = indexedDbClient; // might be other clients
storageInterface.storeMedia = function (blob) {
return currentClient.storeMedia(blob).then(function(mediaPath) {
return currentClient.storeMetadata(eventType, mediaPath);
});
}
});
This is where things get super hairy. What I'm trying to achieve is the following:
storageInterface.storeMedia(superBuffer).then(function (importantStuff) {
// this should go storeMedia > baseStorageClient > eventService
importantStuff.IMPORTANTKEY;
importantStuff.IMPORTANTDATA;
});
But I can't quite figure out how to get this handled. How can I compile a result along a chain of promises like this?
There's two major problems:
You should treat done and fail as deprecated. They don't allow for any chaining, they will discard the results of the callback. Always use then.
sendRequest = function(data){
return $.ajax(data).then(function(response){
return response.IMPORTANTDATA;
}, function(response) {
throw new Error(response);
});
}
Your transaction doesn't return any promise yet, so there's nothing for you to chain onto. You'll need to promisify it first:
function promiseFromRequest(req) {
return new Promise(function(resolve, reject) {
req.onsuccess = resolve;
req.onerror = reject;
});
}
Now you can actually use it like so:
storeMedia = function(blob){
return openDatabase().then(function () {
return promiseFromRequest(database.transaction(storeName, "readwrite")
.objectStore(storeName)
.add(dbEntry))
.then(function (event) {
logger.log("combined segments saved into database.");
return {
IMPORTANTKEY: dbEntry.mediaId
}
}, function (e) {
logger.log("Unable to save segments " + e);
throw e;
};
});
};
With those, you should be able to combine the results from storeMedia and storeMetaData in some way.

NodeJS: promise returned by Promise.all is not resolved although the individual promises are

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;
});
}
}

View all pending promises in javascript

In my tests, sometimes I get timeouts, and it would be very useful to see what where the promises that were pending before the timeout, so that I know what promises have the most chances of being in an "always pending state".
Is there a way to do that ?
Here's an example code :
Promise.resolve().then(function firstFunction() {
console.log(1);
return 1;
}).then(function () {
return new Promise(function secondFunction(resolve, reject) {
// NEVER RESOLVING PROMISE
console.log(2);
});
}).then(function thirdFunction() {
// function that will never be called
console.log(3);
})
setTimeout(function timeoutTest() {
const pendingPromises = [];// ??????????? how do I get the pendingPromises
console.log(pendingPromises);
process.exit();
}, 5000);
I would like, if possible, to get in pendingPromises the name of the function and stacktrace of the promise secondFunction, since it is the one that will never resolve.
A promise chain is designed progressively to deliver values down its success path or a "reason" down its error path. It is not designed to be enquired at some arbitrary point in time for a "snapshot" the state of individual promises that are assimilated by it.
Therefore, a promise chain offers no "natural" way to do what you ask.
You need to contrive a mechanism for :
registering promises of interest.
tracking each registered promise's state (if not already provided by the promise implementation).
retreiving, on demand, the registered promises, filtered by state.
A constructor along these lines will do the job :
function Inspector() {
var arr = [];
this.add = function(p, label) {
p.label = label || '';
if(!p.state) {
p.state = 'pending';
p.then(
function(val) { p.state = 'resolved'; },
function(e) { p.state = 'rejected'; }
);
}
arr.push(p);
return p;
};
this.getPending = function() {
return arr.filter(function(p) { return p.state === 'pending'; });
};
this.getSettled = function() {
return arr.filter(function(p) { return p.state !== 'pending'; });
};
this.getResolved = function() {
return arr.filter(function(p) { return p.state === 'resolved'; });
};
this.getRejected = function() {
return arr.filter(function(p) { return p.state === 'rejected'; });
};
this.getAll = function() {
return arr.slice(0); // return a copy of arr, not arr itself.
};
};
The only methods required by the question are .add() and .getPending(). The others are provided for completeness.
You can now write :
var inspector = new Inspector();
Promise.resolve().then(function() {
console.log(1);
}).then(function() {
var p = new Promise(function(resolve, reject) {
// NEVER RESOLVING PROMISE
console.log(2);
});
return inspector.add(p, '2');
}).then(function() {
// function that will never be called
console.log(3);
});
setTimeout(function() {
const pendingPromises = inspector.getPending();
console.log(pendingPromises);
process.exit();
}, 5000);
fiddle
The use of Inspector isn't confined to promises assimilated by promise chains. It could be used for any arbitrary set of promises, for example a set to be aggregated with Promise.all() :
promise_1 = ...;
promise_2 = ...;
promise_3 = ...;
var inspector = new Inspector();
inspector.add(promise_1, 'promise 1');
inspector.add(promise_2, 'promise 2');
inspector.add(promise_3, 'promise 3');
var start = Date.now();
var intervalRef = setInterval(function() {
console.log(Date.now() - start + ': ' + inspector.getSettled().length + ' of ' + inspector.getAll().length + ' promises have settled');
}, 50);
Promise.all(inspector.getAll()).then(successHandler).catch(errorHandler).finally(function() {
clearInterval(intervalRef);
});
I suggest using library such as Bluebird that is ~6x times faster than native promises, offers useful warnings and additional useful methods like - timeout which might help you with this issue.

Javascript for loop Promises

I have an array of urls like this
var urls = ["www.google.com", "www.yahoo.com"];
And I want to loop though the urls and perform an async task inside the loop and not move on to the next item until the async task has finished. I know you can do this with promises but I have having some trouble with it. Here what I have
var xmlReader = require('cloud/xmlreader.js');
function readResponse_async(xlmString) {
var promise = new Parse.Promise();
xmlReader.read(xlmString, function (err, res) {
if(err) {
promise.reject(err);
} else {
promise.resolve(res);
}
});
return promise;
}
for (i = 0; i < urls.length; i++) {
Parse.Cloud.httpRequest({
url: unionUrls[i],
}).then(function(httpResponse) {
try {
// console.log(httpResponse.text)
return readResponse_async(httpResponse.text)
} catch (e) {console.log(e)}
}
But right now it doesn't wait for the readResponse_async to finish, how can I have it wait for that?
Thanks
EDIT
After reading the response I make a save to my database and I have another array like this
var location = ['USA', 'England'];
And I make the save like this
function saveLoc_async(data, location) {
var i3, i4, i5, m,
TestItem = Parse.Object.extend("TestItem"),//can be reused within the loops?
promise = Parse.Promise.as();//resolved promise to start a long .then() chain
for (i3 = 0; i3 < data.count(); i3++) {
(function(testItem) {
testItem.set("item", data.at(i));
testItem.set("location", location);
//build the .then() chain
promise = promise.then(function() {
return testItem.save();
});
})(new TestItem());
//************************
//CALL retry(); here?
//**************************
}
Because with your answer I have
function retry() {
if (urlsUnion.length > 0) {
var nextUrl = urlsUnion.pop();
//********** ADDED LINE
var nextLoc = location.pop();
Parse.Cloud.httpRequest({
url: nextUrl,
}).then(function(httpResponse) {
xmlReader.read(httpResponse.text, function (err, res) {
if(err) {
// show an error
} else {
//********** ADDED LINE
saveLoc_async(res, nextLoc);
retry();
}
});
});
}
}
SO where should retry(); go because right now with the save sometimes it puts the second location with one of the first items url? why would that happen?
I did something similar to this for an animation.
var actions = [drawXXX, fadeOutYYY, drawXYZ];
this.startAnimation = function () {
actions.reduce(function (previousAction, nextAction) {
return previousAction.then(nextAction)
}, $.when());
}
Your code fires both urls immediately, and does not wait in-between.
What you would have to do is to remove the first url from the array and fire it. In the 'then' branch check if you still have url's in the array and repeat.
Like this (untested, edited to make the code clean again):
var xmlReader = require('cloud/xmlreader.js');
function readResponse_async(xlmString) {
xmlReader.read(xlmString, function (err, res) {
if(err) {
// show an error
} else {
readFirstUrl();
}
});
}
function readFirstUrl() {
if (urlsUnion.length == 0) {
return;
}
var url = urlsUnion.pop();
Parse.Cloud.httpRequest({
url: url,
}).then(function(httpResponse) {
readResponse_async(httpResponse.text);
});
}
readFirstUrl();
Not sure I understand your use of unionUrls array, but if you have your URL's in a urls array, I think this is pretty clean:
function getUrl(url) {
return Parse.Cloud.httpRequest(url)
.then( function(httpResponse) {
return readResponse_async(httpResponse.text);
});
}
urls.reduce( function(prev, url) {
return prev ? prev.then( function() { getUrl(url); }) : getUrl(url);
}, null);

How to reuse promises?

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.

Categories

Resources