Как убедиться, что $.each отправляет все отсрочки в jQuery?

У меня есть следующий код jQuery:

    myFunc: function(cmd, obj){ 
        var idToExtMap = this. map;
        var requireRes = this.reqInst;
        var deferreds = [];
        var ret = true;
        $.each(idToExtMap[cmd], function(key, ext){
                if(ext.$classIns && ext.$classIns.prepare) {
                    var returnedValue = ext.$classIns.prepare(obj);
                    deferreds.push(returnedValue);
                    $.when(returnedValue).done(function(satisfied){
                        if(ret!==false){
                            ret = satisfied;
                        }
                    });
                } else {
                    requireRes(ext).done(function(){
                        var cls = $.toFunction(ext.$jscls);
                        if(cls) {
                            ext.$classIns = new cls();
                            if(ext.$classIns.prepare){
                                var returnedValue = ext.$classIns.prepare(obj);
                                deferreds.push(returnedValue);
                                $.when(returnedValue).done(function(satisfied){
                                    if(ret!==false){
                                        ret = satisfied;
                                    }
                                });
                            }
                        }
                    });     
                }   
            });

            $.when.apply(null, deferreds).done(function(){
                return ret;
            });
        }

Проблема, с которой я сталкиваюсь, заключается в том, что $.when.apply выполняется до того, как все отложенные запросы будут помещены в массив отложенных. Как я могу убедиться, что $.when.apply выполняется только после того, как все deferreds будут помещены в массив deferreds?


person Dan    schedule 25.04.2015    source источник


Ответы (2)


Главное, что вам нужно сделать, это убедиться, что промисы помещаются в массив синхронно. Когда deferreds.push(...) скрыто внутри обратного вызова done, push() является асинхронным, и массив гарантированно остается пустым при выполнении $.when.apply(...).

Некоторые другие проблемы также могут быть исправлены:

  • Повторения кода вокруг ext.$classIns.prepare(obj) можно избежать путем перестановки.
  • громоздкой внешней переменной ret можно избежать, используя отказ от промисов.

С некоторой другой незначительной уборкой я закончил с этим (непроверенным):

myFunc: function(cmd, obj) {
    var requireRes = this.reqInst;
    var promises = $.map(this.map[cmd], function(key, ext) {
        var p; // p for promise
        if(ext.$classIns) {
            p = $.when(ext.$classIns);
        } else {
            p = requireRes(ext).then(function() {
                var cls = $.toFunction(ext.$jscls);
                if(cls) {
                    ext.$classIns = new cls();
                }
                return ext.$classIns || null;
            });
        }

        /* At this point, `p` is a promise resolved with either a previously created `cls()` object or a freshly created one */

        // As we are inside `$.map(...)`, `return p.then(...)` causes the promise delivered by `p.then(...)` to be pushed onto the `promises` array.
        return p.then(function($classIns) {
            if($classIns && $classIns.prepare) {
                return $classIns.prepare(obj).then(function(satisfied) {
                    if(!satisfied) {
                        // Here, returning a rejected promise is equivalent to setting the original `ret` irrevocably to `false`.
                        return $.Deferred().reject(new Error(".prepare() not satisfied"));
                    }
                });
            } else {
                // You may want also to reject if `$classIns` or `$classIns.prepare` doesn't exist.
                // If not, then delete this else{} clause.
                return $.Deferred().reject(new Error(".prepare() could not be called"));
            }
        });
    });

    /* At this point, `promises` is an array of promises - some or all resolved, some or all pending */

    // Here, instead of the boolean `ret`, you can exploit the joined promise's success/error paths :
    // * success path is equivalent to ret == true.
    // * error path is equivalent to ret == false, or any unpredicted error.
    return $.when.apply(null, promises).then(function() {
        // Success.
        // Do whatever is necessary here or in `myFunc().then(function() {...})`.
    }, function(e) {
        // An error occurred.
        // Do whatever is necessary here or in `myFunc().then(null, function() {...})`.
        console.error(e); //for example
    });
}

Комментарии должны объяснять, что происходит.

person Roamer-1888    schedule 25.04.2015

Переместите $.when.apply() за пределы/после цикла .each(), чтобы вы не вызывали его до тех пор, пока не закончите построение массива deferreds.

Хотя у тебя другие проблемы. Похоже, вы пытаетесь вернуть значение из $.when().done(). Поскольку у вас есть структурированный код, это ничего не даст. Вам нужно будет либо вернуть обещание из вашей функции, либо добавить обратный вызов в вашу функцию, которую вы затем сможете вызвать, когда получите окончательный результат. Это связано с тем, что ваши операции являются асинхронными и завершатся после возврата из основной функции.

person jfriend00    schedule 25.04.2015