jQuery 当延迟承诺未按预期工作时

jQuery When Not Working as expected with Deferred Promises

所以我已经对此做了很多故障排除,并且正在用头撞墙。大多数情况下,我非常熟悉 promises 及其工作原理,并在一些项目中使用过它们。我在完成对 Google 日历 API 中不同日历数据的多次调用以及脚本计算结果数组长度的过程中遇到了一些麻烦回调函数。这是相关代码:

(function($){
    var baseUrl = 'https://www.googleapis.com/calendar/v3/calendars/',
        apiKey = 'xxxxxxxxxxxx',
        calendarData = [],
        events = [],
        allCalendars = [],
        eventsToDisplay = 9;

    /* Get all the calendars that we have registered */
    function getCalendars() {
        return $.getJSON(ajaxurl, {action: 'wps_get_calendars'});
    }

    /*  Get the events for a calendar by the calendar ID */
    function getCalendarEvents(calendarID) {
        return $.getJSON(baseUrl + calendarID + '/events', {
            maxResults: '4',
            orderBy: 'startTime',
            timeMin: moment().format(),
            singleEvents: true,
            key: apiKey
        }).success(function(data){
            calendarData.push(data.items);
    });

    /*  Create a collection of promises */
    var promises = getCalendars().then(function(calendars){
        var deferreds = [];
        calendars.forEach(function(calendar){
            deferreds.push( 
                getCalendarEvents(calendar.googleCalendarId)
            );
        });
        return deferreds;
    });

    /*  Wait until all the promises have completed, then sort the events */
    $.when.apply($, promises).then(concatEvents);

})(jQuery);

基本上问题出在最后一次调用 $.when 时,因为我正在等待我必须完成的一系列承诺。 $.when 似乎不起作用,因为如果我尝试在 $.when 回调中将 calendarData 数组记录到控制台,它 returns 一个没有的数组计算的长度。我能够弄清楚如何执行此操作的唯一方法是在回调中使用 setTimeout,并将其设置为大约 2000 毫秒,但这并不理想,因为取决于网络连接,API可用性等,接收所有数据的时间可能完全不同。

正如我在控制台中看到的那样,当我尝试在 $.when 回调中记录结果时,我得到了这个 "length-less" 数组,该回调无法迭代通过,因为脚本似乎认为它是空的:

知道我做错了什么吗?在此先感谢您的帮助。

有一段时间没和他们一起玩了,但你应该可以接受这个 运行 。

/*  Create a collection of promises */
var control = $.Deferred();
var deferreds = [];
getcalendars().done(function(calendars){
    calendars.forEach(function(calendar){
        deferreds.push( 
            getCalendarEvents(calendar.googleCalendarId)
        );
    });
    control.resolve();
});

control.done(function(){
  /*  Wait until all the promises have completed, then sort the events */
  $.when.apply($, deferreds).done(function() {concatEvents});    })

您的代码在工作方式上存在几个结构性问题。我发现的问题:

  1. 不要使用已弃用的 .success()。使用 .then().
  2. 您正在尝试 return 从一个承诺中提取一系列承诺。相反,使用 $.when() 来 return 一个解析为结果数组的承诺。
  3. $.when() 的结果 return 非常奇怪。它 return 将它们作为单独的参数,而不是结果数组(它不像 ES6 标准 Promise.all() 那样工作)。
  4. jQuery Ajax 调用解析为三个参数并与 $.when() 非常奇怪地交互,如果你直接使用 jQuery ajax 承诺 $.when().
  5. 您依赖于更高范围变量中的副作用,而不是仅仅使用所需数据解决承诺(不需要更高范围变量来跟踪数据)。这使得代码更多 self-contained 并且可重用并且不受竞争条件的限制(如果在多个地方使用)。
  6. 重新构造 getCalendar(),使其 return 成为解析为日历数组的单个承诺,从而使其更易于使用。

这应该可以让您获得想要的结果,并且可以消除您依赖的一些更高范围的变量和副作用。

(function($){
    var baseUrl = 'https://www.googleapis.com/calendar/v3/calendars/',
        apiKey = 'xxxxxxxxxxxx',
        events = [],
        eventsToDisplay = 9;

    /* Get all the calendars that we have registered */
    function getCalendars() {
        return $.getJSON(ajaxurl, {action: 'wps_get_calendars'}).then(function(calendars){
            var promises = calendars.map(function(calendar) {
                return getCalendarEvents(calendar.googleCalendarId);
            });
            return $.when.apply($, promises).then(function() {
                // convert arguments to a single array as our resolved value
                return [].slice.call(arguments);
            });
        });
    }

    /*  Get the events for a calendar by the calendar ID */
    function getCalendarEvents(calendarID) {
        return $.getJSON(baseUrl + calendarID + '/events', {
            maxResults: '4',
            orderBy: 'startTime',
            timeMin: moment().format(),
            singleEvents: true,
            key: apiKey
        }).then(function(data){
            // make resolved value be just data.items
            return data.items;
    });

    /*  get all calendars */
    getCalendars().then(function(calendars){
        // process array of calendars here
        console.log(calendars);
    });

})(jQuery);

此外,很容易对 $.when() 的工作原理感到困惑。这里有一些一般信息来解释它。

$.when() 未解析为结果数组。相反,它将每个结果作为单独的参数传递给回调。所以,如果你有三个结果,那么它会这样做:

$.when(p1, p2, p3).then(function(r1, r2, r3) {
    console.log(r1, r2, r3);
});

然后,除此之外,jQuery Ajax 调用也不会解析为单个值,而是解析为三个值。因此,当您将 N jQuery ajax 承诺传递给 $.when() 时,您会得到 N 个回调参数,其中每个参数都是一个包含三个值的数组([=67= 中的三个值) ] Ajax承诺)。


因此,如果您想在 $.when() 中处理任意数量的承诺结果,您必须使用传递给回调的 arguments 对象并对其进行迭代。

function processWhenResults() {
    for (var i = 0; i < arguments.length; i++) {
        // output each ajax result
        // arguments[i] is an array of results for each corresponding ajax call
        // arguments[i][0] is the actual Ajax result value
        console.log(arguments[i][0]);
    }
}


$.when.apply($, promises).then(processWhenResults);

或者,您可以像我在上面建议的代码中所做的那样,将参数对象转换为结果数组,以便您可以在其上使用普通数组函数。


此外,.success() 已被弃用。你不应该使用它。请改用 .then()