Can I untangle this nesting of 'when' promise invocations? - javascript

I'm new to the when.js javascript library, but I'm familiar with async programming in C#. That's why I find this code to be unwieldy:
filters.doFilter('filter1name', reqAndPosts).then(function(filter1) {
filters.doFilter('filter2name', filter1).then(function(filter2) {
filters.doFilter('filter3name', filter2).then(function (posts) {
renderView(posts);
});
});
return filter1;
});
I basically want three methods to be called in sequence, with the output of each being piped to the next method. Is there anyway I can refactor this code to be more "sequence-like" - i.e. get rid of the nesting? I feel like there's something I'm missing with the when-framework here. I'm not doing it right, right?

Since doFilter returns a promise, we can do
filters.doFilter('filter1name', reqAndPosts)
.then(function(filter1) {
return filters.doFilter('filter2name', filter1);
})
.then(function(filter2) {
return filters.doFilter('filter3name', filter2);
})
.then(renderView);

There is another option to have both advantages of cleaner indentation and previous results available: using withThis.
filters.doFilter('filter1name', reqAndPosts).withThis({}).then(function(filter1) {
this.filter1 = filter1; // since we used withThis, you use `this` to store values
return filters.doFilter('filter2name', filter1);
}).then(function(filter2) {
// use "this.filter1" if you want
return filters.doFilter('filter3name', filter2);
}).then(renderView);

With a little thought you can write a generalised utility function that will take a start object and a filter sequence as its arguments, dynamically build the required .then chain, and return a promise of the multi-filtered result.
The function will look like this ...
function doFilters(filterArray, startObj) {
return filterArray.reduce(function(promise, f) {
return promise.then(function(result) {
return filters.doFilter(f, result);
});
}, when(startObj));
}
... which is an adaptation of a pattern given here in the section headed "The Collection Kerfuffle".
For the operation you want, call as follows :
doFilters(['filter1name', 'filter2name', 'filter3name'], reqAndPosts).then(function(result) {
//All filtering is complete.
//Do awesome stuff with the result.
});
Provding it is not destroyed and is in scope, doFilters() will remain available to be used elsewhere in your code :
doFilters(['f1', 'f2', 'f3'], myOtherObject).then(function(result) {
//...
});
With very little more effort, you could tidy things up by phrasing doFilters() as a method of filters. That would be best of all.

Related

Return result of .then() lambda expression as function result

I'm relatively new to js so please forgive me if my wording isn't quite right. I've also created a jsfiddle to demonstrate the issue.
Overview
In the app I'm working on, I have a function with a jquery ajax call, like this:
function scenario1(ajaxCfg) {
return $.ajax(ajaxCfg)
}
I want to change this function, but without in any way changing the inputs or outputs (as this function is called hundreds of times in my application).
The change is to make a different ajax call, THEN make the call specified. I currently have it written like this:
function callDependency() { //example dependency
return $.ajax(depUri)
}
function scenario2(ajaxCfg) {
return callDependency().then(() => $.ajax(ajaxCfg))
}
Desired Result
I want these two returned objects to be identical:
let result1 = scenario1(exampleCall)
let result2 = scenario2(exampleCall)
More specifically, I want result2 to return the same type of object as result1.
Actual Result
result1 is (obviously) the result of the ajax call, which is a jqXHR object that implements the promise interface and resolves to the same value as result2, which is a standard promise.
Since result2 is not a jqXHR object, result2.error() is undefined, while result1.error() is defined.
I did attempt to mock up these methods (simply adding a .error function to the return result, for example), but unfortunately even when doing this, result1.done().error is defined while result2.done().error is undefined.
Wrapping (or unwrapping) it up
In a nutshell, I want to return the jqXHR result of the .then() lambda function in scenario2 as the result of the scenario2 function. In pseudocode, I want:
function scenario2(ajaxCfg) {
return callDependency().then(() => $.ajax(ajaxCfg)).unwrapThen()
} //return jqXHR
What about something like this? The approach is a little different, but in the end you can chain .done() etc. to the scenario2() function:
const exampleCall = { url: 'https://code.jquery.com/jquery-1.12.4.min.js'};
const depUri = { url: 'https://code.jquery.com/jquery-1.12.4.min.js'};
function callDependency() { //example dependency
return $.ajax(depUri).done(() => console.log('returned callDependancy'))
}
let obj = { //creating an object with the scenario2 as a method so that I can bind it with defer.promise()
scenario2: function(ajaxCfg) {
return $.ajax(ajaxCfg).done(() => console.log('returned senario2')) // Purposely NOT calling the exampleCall() function yet
}
}
defer = $.Deferred(); // Using some JQuery magic to be able to return a jqXHR
defer.promise(obj); // Set the object as a promise
defer.resolve(callDependency()); // Invoking the callDependency() by default on promise resolve
obj.done(() => {
obj.scenario2() // Resolving so the callDependency() function can be called
}).scenario2(exampleCall).done(() => { // Here you can invoke scenario2 and FINALLY chain whatever you want after everything has been called
console.log('Here I can chain whatever I want with .done\(\) or .fail\(\) etc.')
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
What I think is cool about this way of doing it is that you can just keep adding methods to the object that you created, and then all your secondary functions that are built on top of callDependency() can be in one place. Not only that, but you can reuse those same methods on top of other AJAX calls.
Read more about this here.
I hope this helps!
I feel like your life would be made a lot easier if you used async/await syntax. Just remember though that async functions return a promise. So you could instead write:
async function scenario2(ajaxCfg) {
let jqXhrResult;
try {
await callDependency();
jqXhrResult = {
jqXhr: $.ajax(ajaxCfg)
};
} catch() {
// Error handling goes here
}
return jqXhrResult;
}
I actually thought of a way easier way to do this.
You can do it by adding a method to the function constructor's prototype object. That way any created function can inherit that method and you can still use the .done() syntax. It's referred to as prototypal inheritance:
const exampleCall = { url: 'https://code.jquery.com/jquery-1.12.4.min.js'};
const depUri = { url: 'https://code.jquery.com/jquery-1.12.4.min.js'};
function callDependency() {
return $.ajax(depUri).done(() => console.log('returned callDependancy'))
}
Function.prototype.scenario2 = function(ajaxCfg, ...args) {
return this(...args).then(() => $.ajax(ajaxCfg))
}
callDependency.scenario2(exampleCall).done(data => {
console.log(data)
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

Best way to get a javascript function that applies an iteratee function to a list?

Let's say I have an iteratee function that takes an item and returns an item, such as used for map, each functions etc.
Silly example:
function nameConverter(item) {
return {
fullName: item.firstName + ' ' + item.lastName;
}
}
If I have a function taking a callback or having a promise for an item, it's easy to just pass the nameConverter function directly:
return getPerson().then(nameConverter);
However, quite frequently I find myself in a situation where I also need a function taking a list, that iterates over the list with said iteratee function. It's of course very simple to create a one-liner function each time for this:
return listPersons().then(function(persons) {
return persons.map(nameConverter);
});
When doing this frequently it bothered me that there wasn't a shorter syntax, so I created a helper function:
function mapper(iteratee) {
return function(list) {
return _.map(list, iteratee);
}
}
which allowed me to do
return listPersons().then(mapper(nameConverter));
I then further generalized this into:
function iterator(iterate, iteratee) {
return function(list) {
return iterate(list, iteratee);
}
}
so that I could do the same thing for both map, each, filter or whatever underscore/lodash function:
return listPersons().then(iterator(_.map, nameConverter));
However, I am curious if there is a better way, such a standard Javascript function or something in underscore/lodash already that does the same thing? It seems like such a common case...
The iterator function is effectively doing what lodash's partialRight function does i.e. partially applies the rightmost argument to a function.
So in the example above, _.partialRight is a drop in replacement for iterator:
return listPersons().then(_.partialRight(_.map, nameConverter));
NB The comment I added to the question incorrectly uses curryRight where it should be partialRight. curryRight is similar but returns a function where the arguments are applied after the function is first curried:
let curriedConverter = _.curryRight(_.map);
curriedConverter(nameConverter)
or (the uglier)
let curriedConverter = _.curryRight(_.map)(nameConverter);
So the iterator replacement then becomes
return listPersons().then(_.curryRight(_.map)(nameConverter))); // yuk

Return in Revealing Module Pattern

I read Addy's book here about revealing module patter. However, if you execute the example code it actually returns undefined. A fix is to add 'return' before each called functions. Am I supposed to add return for each functions being called if using RMP? Is this the right way to make it work? What am I missing?
var myRevealingModule = (function () {
var privateCounter = 0;
function privateFunction() {
privateCounter++; <--need to add return
}
function publicFunction() {
publicIncrement(); <-- need to add return
}
function publicIncrement() {
privateFunction(); <--need to add return
}
function publicGetCount(){
return privateCounter;
}
// Reveal public pointers to
// private functions and properties
return {
start: publicFunction,
increment: publicIncrement,
count: publicGetCount
};
})();
myRevealingModule.start(); <-return undefined
http://addyosmani.com/resources/essentialjsdesignpatterns/book/#revealingmodulepatternjavascript
The issue nas nothing to do with RMP but rather with functions and return values.
Why would you expect a method that doesn't return anything to actually return something other than undefined?
Take a closer look here. The start in fact calls publicFunction but the body of the latter doesn't return anything.
Yet you call it and expect a value.
The answer to your question is then: yes, if you want a value back from the function, you have to return it.
In this particlar example they have a method count to return current value. Two other methods are just used to control the counter.

flickrapi (js) multiple async calls in a loop

I allmost banged my head into the wall because I can't get the following code too work. I'm trying to code a photo gallery with the flickrApi and have problems with multiple async calls. But perhaps there is a cleaner solution to code this.
openPhotoset() is called when clicking the link of a photoset. Unfortunately getting the description of a photo I need to use a different method, which means another async call. I'm looping through the data, but because I make the call in a loop (that's when I have the photo-id available) the deferred of openPhotoset() doesn't resolve after looping but before. I read and have seen examples of $.when() used in a loop, filling an array with deferreds and checking with $.when but I seem to fail horribly at it. Is this the solution I need or is there another road to salvation? ;)
I want to execute different functions after all calls within openPhotoset() has completed.
function openPhotoset(photosetId) {
var currentPhotoset = [],
deferred = $.Deferred();
_requestPhotosOfSet(photosetId).done(function(data){
$(data.photoset.photo).each(function(i, photo){
var objPhoto = {};
objPhoto.id = photo.id;
objPhoto.title = photo.title;
objPhoto.farm = photo.farm;
objPhoto.server = photo.server;
objPhoto.secret = photo.secret;
// get photo description
requestPhotoInfo(photo.id).done(function(data) {
objPhoto.description = data.photo.description._content;
currentPhotoset.push(objPhoto);
}).then(function() {
// TODO: renders with each iteration, shouldnt!
var template = $('#li-gallery').html(),
result = Mustache.render(template, {currentPhotoset:currentPhotoset});
showGallery();
_$fyGallery.find('.gallery-list').html(result);
deferred.resolve();
});
});
});
return deferred;
}
You can do this by changing .done() for .then() in a couple of places, and rearranging things a bit - well quite
a lot.
I think you've probably been searching for something like this :
function openPhotoset(photosetId) {
return _requestPhotosOfSet(photosetId).then(function(data) {
var promises = $(data.photoset.photo).map(function(photo) {
return requestPhotoInfo(photo.id).then(function(data) {
return {
id: photo.id,
title: photo.title,
farm: photo.farm,
server: photo.server,
secret: photo.secret,
description: data.photo.description._content
};
});
}).get();//.get() is necessary to convert a jQuery object to a regular js array.
return $.when.apply(null, promises).then(function() {
var template = $('#li-gallery').html(),
result = Mustache.render(template, {
currentPhotoset: Array.prototype.slice.apply(arguments)
});
showGallery();
_$fyGallery.find('.gallery-list').html(result);
});
});
}
The main difference here is the creation of an array of promises as opposed to an array of photo objects, and allowing the promises to convey the data. This allows $.when() to fire off a callback when all the promises are fulfilled - ie when data objects have been composed for all photos in the set.
Note the use of .map() instead of .each(), thus simplifying the creation of promises.
And finally, the overall promise returned by openPhotoset() allows whatever action to be taken on completion of the whole process. Just chain .then().
openPhotoset(...).then(function() {
// here, do whatever
});
EDIT
The overall pattern is probably easier to understand if the inner workings are pulled out and rephrased as named promise-returning functions - getPhotoInfoObject() and renderData().
function openPhotoset(photosetId) {
function getPhotoInfoObject(photo) {
return requestPhotoInfo(photo.id).then(function(data) {
//$.extend() is much less verbose than copying `photo`'s properties into a new object longhand.
return $.extend(photo, {description: data.photo.description._content});
});
}
function renderData() {
var template = $('#li-gallery').html(),
currentPhotoset = Array.prototype.slice.apply(arguments),
result = Mustache.render(template, {
currentPhotoset: currentPhotoset
});
showGallery();
_$fyGallery.find('.gallery-list').html(result);
}
// With the inner workings pulled out as getPhotoInfoObject() and renderData(),
// the residual pattern is very concise and easier to understand.
return _requestPhotosOfSet(photosetId).then(function(data) {
var promises = $(data.photoset.photo).map(getPhotoInfoObject).get();
return $.when.apply(null, promises).then(renderData);
});
}
I was so blinded by the defereds and $.when function that I didn't notice all I needed was to create a counter and count down each time requestPhotoInfo was done and after render the html

Jquery Piping Multiple results

I have three methods
loadMeta, loadData, and renderList.
loadMeta pipes its result to loadData. LoadData uses the meta to get its results;
Then I need to to get the result from loadMeta and loadData and pass them both to renderList. But I don't know how to get both sets of results, I just end up with the result from loadData.
This is (basically) what i'm trying to do:
$.when(loadMeta().pipe(loadData)).then(function(){ renderList(metaResult, dataResult); } );
is it possible to do it inline or do I need to break the statements up?
It would be the easiest if your loadData function would return both results.
A simple function to let the result be an object containing both results would need an additional then (pipe):
loadMeta().then(function(metaResult) {
return loadData(metaResult).then(function(dataResult) {
return {meta:metaResult, data:dataResult};
});
}).done(function(result) {
renderList(result.meta, result.data);
});
Or, you merge them to a resolve callback with just the right signature for renderList:
loadMeta().then(function(metaResult) {
return loadData(metaResult).then(function(dataResult) {
return new $.Deferred().resolve(metaResult, dataResult);
});
}).done(renderList);
Or, as you suggested, we might break the statement and add an additional callback to cache the metaResult:
var metaResult;
loadMeta().done(function(result) {
metaResult = result;
}).then(loadData).done(function(dataResult) {
renderList(metaResult, dataResult);
});
This might be the easiest to understand.

Categories

Resources