Как полностью избежать обещаний jQuery при объединении двух асинхронных функций jQuery?

Я видел много руководств по новым обещаниям EMCA, в которых пропагандируется отказ от «обещаний» в библиотеке jQuery. Обычно говорят, что от них можно увернуться, сделав что-то вроде этого:

Promise.resolve($.getJSON(url, params)); // voila!  the jQuery promise is "gone"!

Однако на самом деле это не работает, когда мне нужно связать вместе две асинхронные функции jQuery. Как связать вместе два вызова getJSON (где второй вызов зависит от первого) без использования jQuery then() или .when()?

Вместо этого я хочу использовать только Promise.all и т.д.

Я думаю, что аналогичная проблема с вопросом будет заключаться в чередовании обещаний jquery и EMCA?


person user3928094    schedule 09.07.2015    source источник


Ответы (2)


Вы можете выбрать любой из двух подходов...

Преобразовать, а затем объединить:

var p1 = Promise.resolve($.getJSON(url_1, params_1)); // voila 1!
var p2 = Promise.resolve($.getJSON(url_2, params_2)); // voila 2!
var p3 = Promise.all([p1, p2]).then(...);

Объединить, а затем преобразовать:

var p1 = $.getJSON(url_1, params_1);
var p2 = $.getJSON(url_2, params_2);
var p3 = Promise.resolve($.when(p1, p2)).then(...); // voila 1 and 2!

Напрямую, любой подход даст вам собственное обещание ES6, p3, которое разрешается, когда разрешаются оба обещания jQuery, или отклоняется, когда одно из обещаний терпит неудачу.

Однако вас, вероятно, интересуют результаты двух вызовов getJSON(), а jQuery в этом отношении неудобен. Промисы jqXHR в jQuery передают несколько параметров своим обратным вызовам успеха и ошибки, тогда как промис ES6 принимает только один; остальные будут игнорироваться. К счастью, довольно просто объединить несколько параметров в один объект. Это нужно сделать в jQuery перед преобразованием в ES6.

Код «преобразовать, затем объединить» расширяется следующим образом:

var p1 = Promise.resolve($.getJSON(url_1, params_1).then(
    function(data, textStatus, jqXHR) {
        return { data:data, textStatus:textStatus, jqXHR:jqXHR };
    },
    function(jqXHR, textStatus, errorThrown) {
        return { jqXHR:jqXHR, textStatus:textStatus, errorThrown:errorThrown };
    }
));
var p2 = Promise.resolve($.getJSON(url_2, params_2).then(
    function(data, textStatus, jqXHR) {
        return { data:data, textStatus:textStatus, jqXHR:jqXHR };
    },
    function(jqXHR, textStatus, errorThrown) {
        return { errorThrown:errorThrown, textStatus:textStatus, jqXHR:jqXHR };
    }
));
var p3 = Promise.all([p1, p2]).then(
    function(results) {
        // results[0] will be an object with properties .data, .textStatus, .jqXHR 
        // results[1] will be an object with properties .data, .textStatus, .jqXHR 
    },
    function(rejectVal) {
        // rejectVal will be an object with properties .errorThrown, .textStatus, .jqXHR
    }
);

Подход «объединить, а затем преобразовать» немного сложнее, поскольку объединенные результаты отображаются (в jQuery) в виде списка arguments, который сам необходимо преобразовать (все еще в jQuery) в массив.

var p1 = $.getJSON(url_1, params_1).then(
    function(data, textStatus, jqXHR) { 
        return { data:data, textStatus:textStatus, jqXHR:jqXHR }; 
    },
    function(jqXHR, textStatus, errorThrown) { 
        return { errorThrown:errorThrown, textStatus:textStatus, jqXHR:jqXHR }; 
    }
);
var p2 = $.getJSON(url_2, params_2).then(
    function(data, textStatus, jqXHR) { 
        return { data:data, textStatus:textStatus, jqXHR:jqXHR };
    },
    function(jqXHR, textStatus, errorThrown) { 
        return { errorThrown:errorThrown, textStatus:textStatus, jqXHR:jqXHR }; 
    }
);
var p3 = Promise.resolve($.when(p1, p2).then(function() {
    return [].slice.call(arguments);// <<< convert arguments list to Array
})).then(
    function(results) { 
        // results[0] will be an object with properties .data, .textStatus, .jqXHR
        // results[1] will be an object with properties .data, .textStatus, .jqXHR
    },
    function(rejectVal) { 
        // rejectVal will be an object with properties .errorThrown, .textStatus, .jqXHR
    }
);

ДЕМО: решено

ДЕМО: отклонено

person Roamer-1888    schedule 10.07.2015
comment
Отличная работа! Я просто должен сказать, насколько я ценю этот ответ. Я прочитал пару статей, в которых упоминается приведение вызовов jQuery.ajax() к нативным промисам, но ни в одной из них не упоминается захват всех параметров, передаваемых методам обратного вызова, когда ajax выполняется. - person Silkster; 09.09.2016
comment
Спасибо. Оглядываясь назад, можно сказать, что ребята из jQuery должны были с самого начала связать такие параметры. Их новаторский дух 10 лет назад оставил им несколько неприятных проблем с наследием в будущем. - person Roamer-1888; 10.09.2016
comment
Это отличный ответ, спасибо! Тем не менее, в jQuery 3.0 jQuery.Deferred теперь Promises/A+ совместим. - person Avraham; 13.07.2017
comment
@Avraham, спасибо за комплимент. Вы предлагаете, чтобы совместимость Promises/A+ изменила мой ответ? - person Roamer-1888; 13.07.2017
comment
@ Roamer-1888, нет необходимости переписывать, так как это справедливо для предыдущих версий и весьма полезно. Однако вы можете добавить пример, показывающий, что jQuery 3 изначально работает с промисами. Посмотрите мой jsfiddle - person Avraham; 14.07.2017
comment
@ Авраам, я не думаю, что третий пример нужен. Соответствие jQuery 3 A+ действительно помещает jQuery в клуб A+, но этот вопрос касается ассимиляции обещаний jQuery, что всегда было возможно. Код в вашей скрипте будет работать под jQuery 1 и 2, а также 3. - person Roamer-1888; 14.07.2017

Обещания JavaScript совместимы. Вы можете комбинировать их по своему усмотрению, все подходящие библиотеки1 и нативные промисы принимают thenables2 из любой реализации откуда3. Если появится что-то инородное, на нем просто сделают Promise.resolve.

Поэтому обычно вы пишете свой код так, как будто все они используют одну и ту же реализацию промисов, и это просто работает.
Однако теперь вы хотите убедиться, что все .then вызовы методов используют вашу любимую реализацию; или вы хотите использовать нестандартный метод или функцию? Для этого вам нужно будет явно присвоить все промисы, для которых вы непосредственно вызываете методы, и ничего больше.

Несколько примеров:

Promise.all([$.ajax(…), $.ajax(…)]).then(…); // just works!
$.ajax(…) // a jQuery promise
.then(…)  // so this would be jQuery `then`, which we don't want.
Promise.resolve($.ajax(…)) // explicit cast
.then(function(data) {     // native `then`
    return $.ajax(…);      //   just works!
})                         // returns a native promise still
.catch(…)                  // so we can use its features

1: Да, jQuery не входит в их число до версии 3.0
2: все отложенные и промисные функции jQuery такие затемные, хотя
3: на самом деле везде< /strong> вы ожидаете обещание, в Promise.resolve, then возвращаемых значений обратного вызова, Promise.all аргументов, …

person Bergi    schedule 09.07.2015
comment
Обратите внимание, что одной из целей Promises/A+ была совместимость с промисами jQuery, тогда вы можете вернуть промисы jQuery в Promises/A+, и он будет правильно их использовать. Как вы указываете, другое направление верно только с 3.0. Может быть полезно сказать, что возврат значения из цепочки then или передача значений методу агрегирования, такому как .all и т. д., является неявным Promise.resolve.. - person Benjamin Gruenbaum; 10.07.2015