最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Turn callback into promise - Stack Overflow

programmeradmin2浏览0评论

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

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);
};
Share Improve this question edited Mar 9, 2014 at 21:03 seasick asked Feb 23, 2014 at 10:30 seasickseasick 1,2452 gold badges17 silver badges29 bronze badges 0
Add a ment  | 

4 Answers 4

Reset to default 5

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 prises 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.

... 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.

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/zsKnK/7/ for the full working document.

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);
});
发布评论

评论列表(0)

  1. 暂无评论