I am attempting to check if the current user is the owner of the record being edited and if so, I would allow the edit, if not I would prevent the edit. I have two functions that get me the current user and the user who created the record respectively. These are called separately since there are different dependencies. How can I combine these to a single function.
Based on a comments below and some debugging, I refactored my code to use promise.all. It is now functioning correctly and is a sample of how to code.
My code is the following:
<!-- Download SPServices from: //spservices.codeplex.com/ -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery.SPServices/2014.02/jquery.SPServices-2014.02.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
//don't exectute any jsom until sp.js file has loaded.
SP.SOD.executeFunc('sp.js', 'SP.ClientContext', StartProcess);
});
function StartProcess() {
var selectedValue = $("h3:contains('Request Phase')").closest('tr').find('select').val();
if (selectedValue == 'New') {
const DataFetch = Promise.all([
makeFirstCall().catch(error => {
return "error";
}),
makeSecondCall().catch(error => {
return "error";
})
]).then(resolvedData => {
// do something with resolved data
console.log('resolvedData='+resolvedData);
}).catch(error => {
console.error(error)
})
console.log('Succces');
}
}
const makeFirstCall = () => {
return new Promise(function(resolve, reject) {
//console.log('makeFirstCall');
// make your call
getCurrentUser().then(
function (currentUser) {
var loginId = currentUser.get_id();
//console.log('Current user ID='+loginId);
resolve(loginId);
}
);
});
}
const makeSecondCall = () => {
return new Promise(function(resolve, reject) {
console.log('makeSecondCall ');
// make your call
retrieveListItems().then(
function (requestUser) {
//var loginId = requestUser.get_id();
console.log('requestUser user ID='+requestUser);
resolve(requestUser);
}
);
});
}
function getCurrentUser()
{
//Declare your deferred object here
//console.log('getCurrentUser');
var deferred=$.Deferred();
var ctx = new SP.ClientContext.get_current();
this.website = ctx.get_web();
this.currentUser = website.get_currentUser();
ctx.load(currentUser);
ctx.executeQueryAsync(Function.createDelegate(this,
function () { deferred.resolve(currentUser); }),
Function.createDelegate(this,
function (sender, args) { deferred.reject(sender, args); })
);
//console.log('END-getCurrentUser');
//Return your Promise Object
return deferred.promise();
}
function retrieveListItems()
{
//Declare your deferred object here
console.log('retrieveListItems');
var deferred=$.Deferred();
var ctx = new SP.ClientContext.get_current();
// Get ID of the current item.
var currentItemID = window.location.href.toLowerCase();
currentItemID = currentItemID.substring(currentItemID.toLowerCase().indexOf("?id=") + 4,
currentItemID.toLowerCase().indexOf("&source="));
console.log('currentItemID='+currentItemID);
var oList = clientContext.get_web().get_lists().getByTitle('BSMRequests');
var camlQuery = new SP.CamlQuery();
camlQuery.set_viewXml('<View><Query><Where><Eq><FieldRef Name=\'ID\'/><Value Type=\'Text\'>'+ currentItemID+
'</Value></Eq></Where></Query></View>');
collListItem = oList.getItems(camlQuery);
ctx.load(collListItem);
//var loginId = "66";
//deferred.resolve(loginId);
ctx.executeQueryAsync(Function.createDelegate(this,function () {
var listItemEnumerator = collListItem.getEnumerator();
while (listItemEnumerator.moveNext()) {
var oListItem = listItemEnumerator.get_current();
console.log('Record id='+oListItem.get_item('ID'));
requestorID = oListItem.get_item('Requestor_x0020_Name').get_lookupId();
console.log('requestorID='+requestorID );
}
deferred.resolve(requestorID);
}),
Function.createDelegate(this,function (sender, args) { deferred.reject(sender, args); })
);
console.log('END-retrieveListItems');
//Return your Promise Object
return deferred.promise();
}
</script>
My code will return the two ids and then one can do what they want with those. Those IDs are in an array so comparing them would do the trick.
console.log('Userid: '+resolvedData[0]+'=? Userid: '+resolvedData[1]);
Hi you can make both calls into promises and use promise.all to get the response. That way you wait for both results.
const DataFetch = () => {Promise.all([
makeFirstCall().catch(error => {
return "error";
}),
makeSecondCall().catch(error => {
return "error";
})
]).then(resolvedData => {
// do something with resolved data
}).catch(error => {
console.error(error)
})}
To make the promise function, you can do this:
const makeFirstCall = () => {
return new Promise(function(resolve, reject) {
// make your call
reject(new Error(`error message`)); // if error
resolve(dataFromCall);
});
}
Related
I write promises to take from back-end some data. But function end() don't see my variable witch contains this data. What I'm doing wrong, and how to return overlappingProjects variable?
Console.log(1 and 2) shows massive with data but console.log(4) already have empty object.
this.checkSingleInvitation = function(invitation) {
console.log('Оверлап сингл');
var dtoArray = [];
var overlappingProjects = {};
InvitationService.acceptedProjects.models.forEach(function(accepted) {
if(!(dateHelper.parseDate(accepted.dt_from) > dateHelper.parseDate(invitation.dt_to) || dateHelper.parseDate(accepted.dt_to) < dateHelper.parseDate(invitation.dt_from))) {
var dto = {
target: invitation.project_has_musicians_id,
goal: accepted.project_id
};
dtoArray.push(dto);
}
});
var promises = [];
angular.forEach(dtoArray, (function(dto) {
var deferred = $q.defer();
var overlappingProjects = {};
//async fun
InvitationService.checkOverlapping(dto)
.before(function() {
progressBars.progressbar.requestsInProgress++;
})
.success(function(data) {
// TODO: overlappingProjects - ???
if(Object.keys(data).length) {
console.log('1');
console.log(data);
overlappingProjects = data;
console.log(overlappingProjects);
}
console.log('2');
console.log(data);
deferred.resolve(data);
})
.error(function(error) {
deferred.reject(error);
})
.finally(function() {
progressBars.progressbar.requestsInProgress--;
});
promises.push(deferred.promise);
}));
$q.all(promises).then(console.log(promises)).then(
end()
);
function end() {
console.log('4');
console.log(overlappingProjects);
return overlappingProjects;
}
}
The problem seems to be that you are defining overlappingProjects twice.
Remove the second definition:
angular.forEach(dtoArray, (function(dto) {
var deferred = $q.defer();
var overlappingProjects = {}; // <-- remove this line
In addition to twice variable(overlappingProjects) declaration, you call end function immediately instead of pass it as callback:
$q.all(promises).then(console.log(promises)).then(
end()
);
Should be:
$q.all(promises)
.then(() => console.log(promises)) // may be .then(results => console.log(results)) ?
.then(end);
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.
So for example, lets say I have this code:
var cmd = require('node-cmd')
function getStuff() {
return new Promise((resolve, reject) => {
var dataNStuff;
cmd.get('brew --version', data => {
dataNStuff += data;
})
cmd.get('yarn global ls', data => {
dataNStuff += data;
})
resolve(dataNStuff)
})
}
In this case cmd.get() is async, so I don't know when the data is coming in. I want to be able to have both calls already have the data come in before I resolve(dataNStuff), is this even possible with a Promise, and no I do not want to use a callback in this scenario. Is there a much simpler or faster way of doing the exact same thing?
Using Promises for the solution, use Promise.all, and "promisified" version of cmd.get
var cmd = require('node-cmd');
var cmdPromise = arg => new Promise((resolve, reject) => cmd.get(arg, resolve));
function getStuff() {
return Promise.all([cmdPromise('brew --version'), cmdPromise('yarn global ls')])
.then(results => results.join(''));
}
to "explain" cmdPromise in case that compact version isn't readable, it's basically this:
var cmdPromise = function cmdPromise(arg) {
return new Promise((resolve, reject) => {
cmd.get(arg, data => resolve(data));
});
};
Here's a straightforward solution involving Promises.
function getStuff() {
var dataNStuff = '';
var promiseOne = new Promise(function(resolve, reject) {
cmd.get('brew --version', data => {
dataNStuff += data;
resolve();
});
});
var promiseTwo = new Promise(function(resolve, reject) {
cmd.get('yarn global ls', data => {
dataNStuff += data;
resolve();
});
});
return Promise.all([promiseOne, promiseTwo]).then(function() {
return dataNStuff;
});
}
I assume that cmd.get will execute one after the other. If this assumption is incorrect, then there is no guarantee of the order of the strings. (ie It may display brew before yarn or it may display yarn before brew.)
ES2017 answer: removing the .then() in favor of = via async/await (as the comment from #stephen-bugs-kamenar mentions.
var log = console.log;
var getBanana = function() {
return new Promise(function(resolve, reject) {
setTimeout(function(){
resolve('banana')
}, 2000);
});
}
var getGrape = function() {
return new Promise(function(resolve, reject) {
setTimeout(function(){
resolve('grape')
}, 3000);
});
}
var start = async function(){
var result = await Promise.all([getBanana(), getGrape(), 'apple'])
console.log('All done', result)
}
start();
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);
});
I'm in a situation where multiple parts of my app wants access to data. I would like the first caller to initiate a fetch from the server, whilst subsequent requests should wait for the data to be fetched. How do I do this with a Promise?
I've tried something along these lines, with no success:
var promise = null;
var fetchComplete = false;
var data = null;
function getData() {
if (fetchComplete) {
return new Promise(function(resolve, reject) {
resolve(data);
});
} else {
if (promise === null) {
promise = new Promise(function(resolve, reject) {
getDataFromServer(function(response) {
fetchComplete = true;
data = response;
});
});
}
else {
return promise;
}
}
};
You can use a closure like so:
function getDataPromise() {
return new Promise(function(resolve, reject) {
getDataFromServer(resolve);
});
}
var getDataCached = (function() {
var fetchPromise = null;
return function() {
if (!fetchPromise) {
// Fetch data here and populate fetchPromise with an actual promise.
fetchPromise = getDataPromise();
}
return fetchPromise;
};
})();
The first invokation will populate the fetchPromise closure variable, while the rest would simply return it.