Turn callback into promise

匿名 (未验证) 提交于 2019-12-03 08:46:08

问题:

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

回答1:

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.



回答2:

... jQuery-Deferred should always be resolved. So after finishing your tasks in your function call "dfd.resolve()" with your parameters you have to work with in your then-callback-function.



回答3:

Current JavaScript:

var map = null; var boxpolys = null; var directions = null; var routeBoxer = null; var distance = null; // km var service = null; var gmarkers = []; var infowindow = new google.maps.InfoWindow();  var promises = [];  function MyPromise() {     return this; };  MyPromise.prototype.promise = function () {     var p = promises[this] || {         then: []     };      promises[this] = p;      return this; };  MyPromise.prototype.then = function (func) {     this.promise();      promises[this].then.push(func);      return this; };  MyPromise.prototype.resolve = function () {     this.promise();      var then = promises[this].then;      for (var promise in then) {         then[promise].apply(this, arguments);     }      return this; };  function initialize() {     // Default the map view to the continental U.S.     var mapOptions = {         center: new google.maps.LatLng(40, -80.5),         mapTypeId: google.maps.MapTypeId.ROADMAP,         zoom: 8     };      map = new google.maps.Map(document.getElementById("map"), mapOptions);     service = new google.maps.places.PlacesService(map);      routeBoxer = new RouteBoxer();      directionService = new google.maps.DirectionsService();     directionsRenderer = new google.maps.DirectionsRenderer({         map: map     }); }  function route() {     var dfd = new MyPromise().promise();      // Clear any previous route boxes from the map     clearBoxes();      // Convert the distance to box around the route from miles to km     distance = parseFloat(document.getElementById("distance").value) * 1.609344;      var request = {         origin: document.getElementById("from").value,         destination: document.getElementById("to").value,         travelMode: google.maps.DirectionsTravelMode.DRIVING     }      // Make the directions request     directionService.route(request, function (result, status) {         if (status == google.maps.DirectionsStatus.OK) {             directionsRenderer.setDirections(result);              // Box around the overview path of the first route             var path = result.routes[0].overview_path;             var boxes = routeBoxer.box(path, distance);             // alert(boxes.length);             drawBoxes(boxes);             // findPlaces(boxes,0);             $.search(boxes, 0).then(function (p) {                 console.log("done", p);             }).then(dfd.resolve);         } else {             alert("Directions query failed: " + status);         }     });      // $.when(findPlaces()).done(function(){     //  console.log("done");     // });      return dfd; }  // Draw the array of boxes as polylines on the map function drawBoxes(boxes) {     boxpolys = new Array(boxes.length);     for (var i = 0; i < boxes.length; i++) {         boxpolys[i] = new google.maps.Rectangle({             bounds: boxes[i],             fillOpacity: 0,             strokeOpacity: 1.0,             strokeColor: '#000000',             strokeWeight: 1,             map: map         });     } }   $.search = function findPlaces(boxes, searchIndex) {     var dfd = new MyPromise().promise();      var request = {         bounds: boxes[searchIndex],         types: ["food"]     };      window.place_Results = [];      service.radarSearch(request, function (results, status) {         if (status != google.maps.places.PlacesServiceStatus.OK) {             alert("Request[" + searchIndex + "] failed: " + status);             return;         }          document.getElementById('side_bar').innerHTML += "bounds[" + searchIndex + "] returns " + results.length + " results<br>"          for (var i = 0, result; result = results[i]; i++) {             var marker = createMarker(result);             place_Results.push(result.reference);         }          searchIndex++;          if (searchIndex < boxes.length) findPlaces(boxes, searchIndex);          if (place_Results.length > 0) {             dfd.resolve(place_Results);         }     });      return dfd; }  // Clear boxes currently on the map function clearBoxes() {     if (boxpolys != null) {         for (var i = 0; i < boxpolys.length; i++) {             boxpolys[i].setMap(null);         }     }     boxpolys = null; }  function createMarker(place) {     var placeLoc = place.geometry.location;     if (place.icon) {         var image = new google.maps.MarkerImage(         place.icon, new google.maps.Size(71, 71),         new google.maps.Point(0, 0), new google.maps.Point(17, 34),         new google.maps.Size(25, 25));     } else var image = null;      var marker = new google.maps.Marker({         map: map,         icon: image,         position: place.geometry.location     });     var request = {         reference: place.reference     };     google.maps.event.addListener(marker, 'click', function () {         service.getDetails(request, function (place, status) {             // console.log(place);             if (status == google.maps.places.PlacesServiceStatus.OK) {                 var contentStr = '<h5>' + place.name + '</h5><p>' + place.formatted_address;                 if ( !! place.formatted_phone_number) contentStr += '<br>' + place.formatted_phone_number;                 if ( !! place.website) contentStr += '<br><a target="_blank" href="' + place.website + '">' + place.website + '</a>';                 contentStr += '<br>' + place.types + '</p>';                 infowindow.setContent(contentStr);                 infowindow.open(map, marker);             } else {                 var contentStr = "<h5>No Result, status=" + status + "</h5>";                 infowindow.setContent(contentStr);                 infowindow.open(map, marker);             }         });      });     gmarkers.push(marker);     var side_bar_html = "<a href='javascript:google.maps.event.trigger(gmarkers[" + parseInt(gmarkers.length - 1) + "],\"click\");'>" + place.name + "</a><br>";     document.getElementById('side_bar').innerHTML += side_bar_html; }  initialize();  document.getElementById('route').onclick = route; 

See http://jsfiddle.net/zsKnK/7/ for the full working document.



回答4:

You're resolving your deferred after the first request, not waiting for the results of the recursive calls. To do that, you'll need to chain them. Also, you shouldn't use a global variable for the place_Results.

$.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?                 }                 dfd.resolve(place_results, searchIndex+1);             });             return dfd.then(findNextPlaces);         } else {             return dfd.resolve(place_results).promise();         }     }      return findNextPlaces([], 0); }; 

$.search(boxes,0).then(function(results) {     console.log("done", results); }, function(err) {     alert(err); }); 


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!