Turn callback into promise
javascript
Solution
To answer the question implied by the title, "Turn callback into promise", the simple answer is to use a really simple "promisifier pattern" (my term), in which a Deferred's `.resolve()` method is established as a callback :
Original call with callback :
obj.method(param1, param2, ... paramN, callbackFn);
Converted call, with Deferred wrapper :
$.Deferred(function(dfd) {
obj.method(param1, param2, ... paramN, dfd.resolve);
}).promise();
This can be done whether or not `obj.method` is asynchronous. The advantage is that you now have the full chainability of Deferreds/promises available to you either in the same block of code or, more typically, elsewhere having assigned or returned the generated Promise.
Here's a way in which the pattern might be used in the case of this question ...
$.search = function(boxes, types) {
function findPlaces(box) {
var request = {
bounds: box,
types: types
};
//***********************
// Here's the promisifier
//***********************
return $.Deferred(function(dfd) {
service.radarSearch(request, dfd.resolve);
}).promise();
//***********************
//***********************
//***********************
}
function handleResults(results, status, searchIndex) {
var message, marker;
if (status != google.maps.places.PlacesServiceStatus.OK) {
message = "bounds[" + searchIndex + "] failed : " + status;
}
else {
message = "bounds[" + searchIndex + "] returns " + results.length + " results";
for (var i = 0, result; result = results[i]; i++) {
marker = createMarker(result);
place_Results.push(result.reference);
}
}
return message;
}
var place_Results = [],
p = $.when();//resolved starter promise
//This concise master routine comprises a loop to build a `.then()` chain.
$.each(boxes, function(i, box) {
p = p.then(function() {
return findPlaces(box).done(function(results, status) {
// This function's arguments are the same as the original callback's arguments.
var message = handleResults(results, status, i);
$('#side_bar').append($("<div/>").append(message));
});
});
});
//Now, chain a final `.then()` in order to make the private var `place_Results` available via the returned promise. For good measure, the `types` array is also repeated back.
return p.then(function() {
return {
types: types,
results: place_Results
};
});
};
`$.search()` can now be used as follows :
$.search(boxes, ["food"]).done(function(obj) {
alert(obj.results.length + ' markers were a added for: "' + obj.types.join() + '"');
});
DEMO - Note: jQuery 1.8.3+ is required due to major revision of `jQuery.Deferred.then()` at jQuery 1.8.3 .
This is not exactly equivalent to the code in the question but may be good for diagnosis of the issues you report. In particular, :
- it won't stop on error
- it will put success and error messages in the '#side_bar'.
It should be simple enough to adjust the code to do what you want.
Problem
I'm working with the google maps api and this piece of code is returning a list of places asynchronously. How can I call this function and have it trigger something when all the data is collected? This has been what i've tried so far - ``` $.search = function(boxes) { function findNextPlaces(place_results, searchIndex) { var dfd = $.Deferred(); if (searchIndex < boxes.length) { service.radarSearch({ bounds: boxes[searchIndex], types: ["food"] }, function (results, status) { if (status != google.maps.places.PlacesServiceStatus.OK) { return dfd.reject("Request["+searchIndex+"] failed: "+status); } console.log( "bounds["+searchIndex+"] returns "+results.length+" results" ); for (var i = 0, result; result = results[i]; i++) { var marker = createMarker(result); place_results.push(result.reference); // marker? } }); return dfd.then(findNextPlaces); } else { return dfd.resolve(place_results).promise(); } } return findNextPlaces([], 0); }; ```