【问题标题】:jQuery When Not Working as expected with Deferred PromisesjQuery 在延迟承诺的情况下无法按预期工作
【发布时间】:2017-06-25 06:22:10
【问题描述】:

因此,我已经对此进行了很多故障排除,并且正在将头撞到墙上。在大多数情况下,我对 Promise 及其工作方式非常熟悉,并且在一些项目中使用过它们。我在完成对来自 Google Calendar 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 数组记录到控制台,它会返回一个没有计算长度的数组。我能够弄清楚如何做到这一点的唯一方法是在回调中使用setTimeout,并将其设置为大约 2000 毫秒,但这并不理想,因为取决于网络连接、API 可用性等。 ,接收所有数据的时间可能完全不同。

就像我在控制台中看到的一样,当我尝试在$.when 回调中记录结果时,我得到了这个“长度更少”的数组,因为脚本无法迭代似乎认为它是空的:

知道我在这里做错了什么吗?提前感谢您的帮助。

【问题讨论】:

  • 1) deferred.then 返回一个新的承诺,而不是你返回的数组。 2) 在 getcalendars 解决之前,您将无法访问该数组。 3) 使您的承诺数组超出 .then 的范围,并使用 getcalendars.then 填充它。使用另一个外部延迟,您将从 getcalendars 解决。在最终承诺被推送到数组之后,然后使用它来执行 .apply 到数组
  • $.when() 真的是脑残,在与 jQuery Ajax 调用一起使用时更是如此。它不解析为数组。它解析为 N 个单独的参数,每个参数都可能是一个数组。要处理任意数量的结果,您必须处理在$.when(...).then(fn) 中传递给fnarguments 对象。研究 jQuery 文档中的 $.when() 示例,尤其是与 Ajax 调用一起使用时。

标签: javascript jquery arrays promise deferred


【解决方案1】:

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

  1. 不要使用已弃用的.success()。使用.then()
  2. 您试图从一个 Promise 中返回一个 Promise 数组。相反,在此处使用 $.when() 返回一个可解析为结果数组的 Promise。
  3. $.when() 返回结果的方式非常不稳定。它将它们作为单独的参数返回,而不是作为结果数组(它不像 ES6 标准 Promise.all() 那样工作)。
  4. 如果您将 jQuery ajax 承诺直接与 $.when() 一起使用,则 jQuery Ajax 调用解析为三个参数并与 $.when() 进行非常奇怪的交互。
  5. 您依赖于更高范围变量中的副作用,而不是仅使用所需数据解决承诺(无需更高范围变量来跟踪数据)。这使得代码更加独立和可重复使用,并且不受竞争条件的影响(如果在多个地方使用)。
  6. 重组getCalendar(),使其返回一个可解析为日历数组的promise,从而使其更易于使用。

这应该会得到你想要的结果,并且它会消除你所依赖的一些更高范围的变量和副作用。

(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 个参数给您的回调,其中每个参数是一个包含三个值的数组(来自 jQuery Ajax 承诺的三个值)。


因此,如果您想处理$.when() 中的任意数量的promise 结果,您必须使用传递给您的回调的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()

【讨论】:

  • 添加了有关编码逻辑结构问题的重要附加信息。
  • @brianjohnhanna - 这是否为您解释并回答了您的问题?
  • 我还没有机会尝试,但感谢您的解释!我会尝试并报告
【解决方案2】:

已经有一段时间没有和他们一起玩了,但你应该能够接受并使用它。

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

【讨论】:

  • 因为它确保他的所有承诺在他尝试访问它们之前都是完整的。仅在所有承诺都在数组中之后才完成 when 的设置。 deferreds[] 现在处于可访问的范围内。
猜你喜欢
  • 1970-01-01
  • 2016-07-27
  • 2022-01-23
  • 1970-01-01
  • 2015-03-27
  • 2023-03-21
  • 1970-01-01
  • 2015-08-25
相关资源
最近更新 更多