jQuery откладывает и обещает - .then () против .done ()

473

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

Может кто-нибудь, пожалуйста, просветите меня для правильного использования?

screenm0nkey
источник
15
Обратите внимание, что JQuery 3.0, выпущенный в июне 2016 года, был первой версией, которая соответствовала спецификации Promises / A + и ES2015 Promises. Реализация до этого имела несовместимость с обещаниями.
Flimm
Я обновил свой ответ улучшенной рекомендацией относительно того, что использовать, когда.
Роберт Симер

Ответы:

577

Призванные обратные вызовы done()будут запущены, когда отложенное разрешение будет разрешено. Призванные обратные вызовы fail()будут запущены, когда отложенное отклонено.

До jQuery 1.8 then()был просто синтаксический сахар:

promise.then( doneCallback, failCallback )
// was equivalent to
promise.done( doneCallback ).fail( failCallback )

Начиная с версии 1.8 then()это псевдоним для pipe()и возвращает новое обещание, смотрите здесь для получения дополнительной информации pipe().

success()и error()доступны только для jqXHRобъекта, возвращенного вызовом ajax(). Это простые псевдонимы для done()и fail()соответственно:

jqXHR.done === jqXHR.success
jqXHR.fail === jqXHR.error

Кроме того, done()не ограничивается одним обратным вызовом и будет отфильтровывать не-функции (хотя в версии 1.8 есть ошибка со строками, которая должна быть исправлена ​​в 1.8.1):

// this will add fn1 to 7 to the deferred's internal callback list
// (true, 56 and "omg" will be ignored)
promise.done( fn1, fn2, true, [ fn3, [ fn4, 56, fn5 ], "omg", fn6 ], fn7 );

То же самое и для fail().

Джулиан Обур
источник
8
thenвозвращение нового обещания было ключевым моментом, которого мне не хватало. Я не мог понять, почему цепочка вроде $.get(....).done(function(data1) { return $.get(...) }).done(function(data2) { ... })не работает с data2неопределенным; когда я переключился doneна thenэто, это сработало, потому что я действительно хотел объединить обещания вместе, а не добавлять больше обработчиков к первоначальному обещанию.
wrschneider
5
jQuery 3.0 является первой версией, которая совместима со спецификациями Promises / A + и ES2015.
Flimm
4
Я до сих пор не понимаю, почему я бы использовал один над другим. Если я выполняю ajax-вызов и мне нужно подождать, пока этот вызов не будет полностью завершен (что означает ответ на запрос с сервера), прежде чем я вызову другой ajax-вызов, я использую doneили then? Почему?
Кодирование Йоши
@CodingYoshi Проверьте мой ответ, чтобы, наконец, ответить на этот вопрос (использовать .then()).
Роберт Симер
413

Также есть разница в том, что возвращаемые результаты обрабатываются (это называется цепочкой, doneне цепочкой, а thenпроизводит цепочки вызовов)

promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);
    return 123;
}).then(function (x){
    console.log(x);
}).then(function (x){
    console.log(x)
})

Следующие результаты будут зарегистрированы:

abc
123
undefined

Пока

promise.done(function (x) { // Suppose promise returns "abc"
    console.log(x);
    return 123;
}).done(function (x){
    console.log(x);
}).done(function (x){
    console.log(x)
})

получит следующее:

abc
abc
abc

---------- Обновить:

Btw. Я забыл упомянуть, что если вы возвращаете Promise вместо атомарного значения типа, внешнее обещание будет ждать, пока внутреннее обещание не разрешится:

promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);
    return $http.get('/some/data').then(function (result) {
        console.log(result); // suppose result === "xyz"
        return result;
    });
}).then(function (result){
    console.log(result); // result === xyz
}).then(function (und){
    console.log(und) // und === undefined, because of absence of return statement in above then
})

таким образом, становится очень простым составлять параллельные или последовательные асинхронные операции, такие как:

// Parallel http requests
promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);

    var promise1 = $http.get('/some/data?value=xyz').then(function (result) {
        console.log(result); // suppose result === "xyz"
        return result;
    });

    var promise2 = $http.get('/some/data?value=uvm').then(function (result) {
        console.log(result); // suppose result === "uvm"
        return result;
    });

    return promise1.then(function (result1) {
        return promise2.then(function (result2) {
           return { result1: result1, result2: result2; }
        });
    });
}).then(function (result){
    console.log(result); // result === { result1: 'xyz', result2: 'uvm' }
}).then(function (und){
    console.log(und) // und === undefined, because of absence of return statement in above then
})

Приведенный выше код выдает два http-запроса параллельно, что ускоряет выполнение запросов, тогда как ниже этих http-запросов выполняется последовательно, что снижает нагрузку на сервер

// Sequential http requests
promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);

    return $http.get('/some/data?value=xyz').then(function (result1) {
        console.log(result1); // suppose result1 === "xyz"
        return $http.get('/some/data?value=uvm').then(function (result2) {
            console.log(result2); // suppose result2 === "uvm"
            return { result1: result1, result2: result2; };
        });
    });
}).then(function (result){
    console.log(result); // result === { result1: 'xyz', result2: 'uvm' }
}).then(function (und){
    console.log(und) // und === undefined, because of absence of return statement in above then
})
LU4
источник
121
+1 для понятия, которое doneничего не делает с результатом, где thenизменяется результат. Огромный момент пропущен другими ИМО.
Shanimal
9
Вероятно, стоит упомянуть, к какой версии jQuery это относится, так как поведение thenизменилось в 1.8
bradley.ayers
4
+1 Прямо к делу. Я создал работоспособный пример, если кто-то хочет увидеть, к чему приводят цепочки со смешанными doneи thenзвонками.
Майкл Кропат
7
В приведенном выше примере также подчеркивается, что «done» работает с исходным объектом обещания, созданным изначально, но «then» возвращает новое обещание.
Пулак Канти Бхаттачарья
2
Это относится к jQuery 1.8+. Старые версии действуют так же, как doneпример. Изменение thenк pipeв пре-1.8 , чтобы получить 1.8+ thenповедение.
Дэвид Харкнесс
57

.done() имеет только один обратный вызов, и это обратный вызов успеха

.then() имеет как успешные, так и неудачные обратные вызовы

.fail() имеет только один неудачный обратный вызов

так что это зависит от вас, что вы должны сделать ... вас волнует, если это удастся или не получится?

Марино Шимич
источник
18
Вы не упоминаете, что «then» создает цепочки вызовов. Смотрите ответ Lu4.
oligofren
Ваш ответ с 2011 года ... В настоящее время их возвращаемые значения then()сильно отличаются от done(). Как then()часто называют только при успешном обратном вызове, ваша точка зрения - это скорее деталь, а не главное, что нужно запомнить / знать. (Не могу сказать, как это было до jQuery 3.0.)
Роберт Симер
14

deferred.done ()

добавляет обработчики, которые будут вызываться только после разрешения Deferred . Вы можете добавить несколько обратных вызовов для вызова.

var url = 'http://jsonplaceholder.typicode.com/posts/1';
$.ajax(url).done(doneCallback);

function doneCallback(result) {
    console.log('Result 1 ' + result);
}

Вы также можете написать выше, как это,

function ajaxCall() {
    var url = 'http://jsonplaceholder.typicode.com/posts/1';
    return $.ajax(url);
}

$.when(ajaxCall()).then(doneCallback, failCallback);

deferred.then ()

добавляет обработчики, которые будут вызываться, когда Deferred разрешен, отклонен или все еще выполняется .

var url = 'http://jsonplaceholder.typicode.com/posts/1';
$.ajax(url).then(doneCallback, failCallback);

function doneCallback(result) {
    console.log('Result ' + result);
}

function failCallback(result) {
    console.log('Result ' + result);
}
Nipuna
источник
В вашем посте не ясно, как thenсебя вести, если failобратный вызов не предоставляется, а именно - не fail
BM
В случае сбоя возникает исключение, которое может быть перехвачено верхним уровнем программы. Вы также можете увидеть исключение в консоли JavaScript.
Дэвид Спектор
10

На самом деле есть довольно критическое различие, поскольку jQuery Deferreds предназначены для реализации Promises (а jQuery3.0 фактически пытается привести их в спецификацию).

Ключевое различие между готовым / затем заключается в том, что

  • .done() ВСЕГДА возвращает те же значения Promise / wrapped, с которых он начинался, независимо от того, что вы делаете или что возвращаете.
  • .then() всегда возвращает НОВОЕ Обещание, и вы отвечаете за контроль того, что это Обещание основано на том, какую функцию вы передали ему.

Переведенный из jQuery в собственные обещания ES2015, .done()это своего рода реализация структуры "tap" вокруг функции в цепочке Promise, в которой он, если цепочка находится в состоянии "resol", передает значение функции .. но результат этой функции НЕ повлияет на саму цепочку.

const doneWrap = fn => x => { fn(x); return x };

Promise.resolve(5)
       .then(doneWrap( x => x + 1))
       .then(doneWrap(console.log.bind(console)));

$.Deferred().resolve(5)
            .done(x => x + 1)
            .done(console.log.bind(console));

Те, кто будет регистрировать 5, а не 6.

Обратите внимание, что я использовал done и doneWrap для ведения журнала, а не .then. Это потому, что функции console.log на самом деле ничего не возвращают. И что произойдет, если вы передадите .then функцию, которая ничего не возвращает?

Promise.resolve(5)
       .then(doneWrap( x => x + 1))
       .then(console.log.bind(console))
       .then(console.log.bind(console));

Это войдет:

5

не определено

Что случилось? Когда я использовал .then и передал ему функцию, которая ничего не возвращала, это неявный результат был «undefined» ... который, конечно, возвратил Promise [undefined] для следующего метода then, который записал в журнал undefined. Таким образом, первоначальная ценность, с которой мы начали, была в основном потеряна.

.then()по сути, это форма композиции функции: результат каждого шага используется в качестве аргумента для функции на следующем шаге. Вот почему .done лучше всего рассматривать как «нажатие» -> это на самом деле не часть композиции, а просто что-то, что пробуждает взгляд на значение на определенном шаге и запускает функцию с этим значением, но на самом деле не меняет композиция никак.

Это довольно фундаментальное различие, и, вероятно, есть веская причина, по которой нативные Promises не имеют реализованного метода .done. Нам не нужно разбираться, почему нет метода .fail, потому что он еще более сложен (а именно .fail / .catch НЕ являются зеркалами функций .done / .then -> в .catch, которые возвращают голые значения, не «остаться» отвергается, как те, которые были переданы. затем они решают!)

Dtipson
источник
6

then()всегда означает, что он будет вызван в любом случае. Но передача параметров различна в разных версиях jQuery.

До jQuery 1.8 then()равняется done().fail(). И все функции обратного вызова имеют одинаковые параметры.

Но, начиная с jQuery 1.8, then()возвращается новое обещание, и, если оно вернуло значение, оно будет передано в следующую функцию обратного вызова.

Давайте посмотрим на следующий пример:

var defer = jQuery.Deferred();

defer.done(function(a, b){
            return a + b;
}).done(function( result ) {
            console.log("result = " + result);
}).then(function( a, b ) {
            return a + b;
}).done(function( result ) {
            console.log("result = " + result);
}).then(function( a, b ) {
            return a + b;
}).done(function( result ) {
            console.log("result = " + result);
});

defer.resolve( 3, 4 );

До jQuery 1.8 ответ должен быть

result = 3
result = 3
result = 3

Все resultзанимает 3. И then()функция всегда передает один и тот же отложенный объект следующей функции.

Но начиная с jQuery 1.8, результат должен быть:

result = 3
result = 7
result = NaN

Потому что первая then()функция возвращает новое обещание, а значение 7 (и это единственный параметр, который будет передан) передается следующему done(), поэтому вторая done()запись result = 7. Вторая then()принимает значение 7 в качестве значения aи принимает значение в undefinedкачестве значения b, поэтому вторая then()возвращает новое обещание с параметром NaN, а последняя done()печатает NaN в качестве результата.

JasmineOT
источник
«then () всегда означает, что он будет вызван в любом случае» - неправда. then () никогда не вызывается в случае ошибки внутри Promise.
Дэвид Спектор
Интересный аспект jQuery.Deferred(), заключающийся в том, что a может принимать несколько значений, которые оно правильно передает первому .then(). - Немного странно, хотя ... так как любое следующее .then()не может этого сделать. (Выбранный интерфейс через returnможет возвращать только одно значение.) Нативный язык Javascript Promiseэтого не делает. (Что является более последовательным, если честно.)
Роберт Симер
3

В ответе есть очень простое умственное отображение, которое было немного трудно найти в других ответах:

BM
источник
2

Использовать только .then()

Это недостатки .done()

  • не может быть прикован
  • resolve()вызов блока (все .done()обработчики будут выполняться синхронно)
  • resolve()может получить исключение из зарегистрированных .done()обработчиков (!)
  • исключение в .done()половину убивает отложенного:
    • дальнейшие .done()обработчики будут молча пропущены

Я временно подумал, что это .then(oneArgOnly)всегда требует, .catch()чтобы ни одно исключение не игнорировалось, но это уже не так: unhandledrejectionсобытия регистрируют необработанные .then()исключения на консоли (по умолчанию). Очень разумно! Нет причин использовать .done()вообще.

доказательство

Следующий фрагмент кода показывает, что:

  • все .done()обработчики будут называться синхронными в точкеresolve()
    • зарегистрирован как 1, 3, 5, 7
    • вошел в систему до того, как скрипт провалился
  • исключение в вызывающем .done()влиянииresolve()
    • вошел через ловить вокруг resolve()
  • исключение нарушает обещание от дальнейшего .done()разрешения
    • 8 и 10 не зарегистрированы!
  • .then() не имеет ни одной из этих проблем
    • зарегистрировано как 2, 4, 6, 9, 11 после того, как поток становится бездействующим
    • (среда фрагмента не unhandledrejectionимеет, кажется)

Кстати, исключения из .done()не могут быть правильно пойманы: из-за синхронного шаблона.done() ошибка генерируется либо в точке .resolve()(может быть библиотечный код!), Либо при .done()вызове, который присоединяет виновника, если отложенное уже разрешено.

console.log('Start of script.');
let deferred = $.Deferred();
// deferred.resolve('Redemption.');
deferred.fail(() => console.log('fail()'));
deferred.catch(()=> console.log('catch()'));
deferred.done(() => console.log('1-done()'));
deferred.then(() => console.log('2-then()'));
deferred.done(() => console.log('3-done()'));
deferred.then(() =>{console.log('4-then()-throw');
    throw 'thrown from 4-then()';});
deferred.done(() => console.log('5-done()'));
deferred.then(() => console.log('6-then()'));
deferred.done(() =>{console.log('7-done()-throw');
    throw 'thrown from 7-done()';});
deferred.done(() => console.log('8-done()'));
deferred.then(() => console.log('9-then()'));

console.log('Resolving.');
try {
    deferred.resolve('Solution.');
} catch(e) {
    console.log(`Caught exception from handler
        in resolve():`, e);
}
deferred.done(() => console.log('10-done()'));
deferred.then(() => console.log('11-then()'));
console.log('End of script.');
<script
src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha384-vk5WoKIaW/vJyUAd9n/wmopsmNhiy+L2Z+SBxGYnUkunIxVxAv/UtMOhba/xskxh"
crossorigin="anonymous"
></script>

Роберт Симер
источник
Несколько вещей: 1) Я вижу, что вы говорите, что doneне будет выполнено, если предыдущий сделал исключение. Но почему это было бы тихо проигнорировано, я имею в виду, что произошло исключение, так почему вы говорите, что он молчит? 2) Я презираю Deferredобъект, потому что его API очень и очень плохо сделан. Это слишком сложно и запутанно. Ваш код здесь также не помогает доказать вашу точку зрения, и он имеет слишком много ненужной сложности для того, что вы пытаетесь доказать. 3) Почему doneв индексах 2, 4 и 6 выполняются до 2-го then?
CodingYoshi
Мой плохой, ты определенно заслуживаешь голоса. Что касается вашего комментария об исключении, обычно так работают исключения: как только они возникнут, код после того, как он не будет выполнен. Кроме того, документация jQuery гласит, что она будет выполнена только в том случае, если отложенное разрешение разрешено.
CodingYoshi
@CodingYoshi Здесь другая ситуация: я говорил только о решенных обещаниях / отсрочках. Я не жалуюсь, что остальная часть обработчика успеха не называется, это нормально. Но я не вижу причин, по которым совершенно другой обработчик успеха по успешному обещанию не называется. Все .then()будет вызвано, исключение (в этих обработчиках) поднято или нет. Но сложение / оставшийся .done()перерыв.
Роберт Симер
@CodingYoshi Я значительно улучшил свой ответ, если мне позволено говорить. Код и текст.
Роберт Симер
1

В jQuery 3.0 есть еще одно существенное отличие, которое может легко привести к неожиданному поведению и не упоминалось в предыдущих ответах:

Рассмотрим следующий код:

let d = $.Deferred();
d.done(() => console.log('then'));
d.resolve();
console.log('now');
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>

это выведет:

then
now

Теперь замените done()на then()тот же фрагмент:

var d = $.Deferred();
d.then(() => console.log('then'));
d.resolve();
console.log('now');
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>

вывод сейчас:

now
then

Таким образом, для немедленно разрешенных отсрочек переданная функция done()всегда будет вызываться синхронно, тогда как любой переданный аргумент then()вызывается асинхронно.

Это отличается от предыдущих версий jQuery, где оба обратных вызова вызываются синхронно, как упомянуто в руководстве по обновлению :

Другое изменение поведения, необходимое для соответствия Promises / A +, заключается в том, что обратные вызовы Deferred .then () всегда вызываются асинхронно. Ранее, если обратный вызов .then () был добавлен к отложенному, который уже был разрешен или отклонен, обратный вызов выполнялся бы немедленно и синхронно.

schellmax
источник
-5

.done()завершает цепочку обещаний, удостоверяясь, что больше ничего не может присоединить дальнейшие шаги. Это означает, что реализация обещания jQuery может выдать любое необработанное исключение, поскольку никто не может обработать его, используя.fail() .

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

глеб бахмутов
источник
6
Внимание! Этот ответ будет правильным для нескольких реализаций обещаний, но не для jQuery, в котором .done()нет завершающей роли. В документации сказано: «Так как deferred.done () возвращает отложенный объект, другие методы отложенного объекта могут быть связаны с этим, включая дополнительные методы .done ()». .fail()не упоминается, но, да, это тоже может быть приковано.
Roamer-1888
1
Мой плохой, не проверял jQuery
глеб бахмутов
1
@glebbahmutov - может быть, вы должны удалить этот ответ, чтобы другие не запутались? Просто дружеское предложение :)
Андрей
2
Пожалуйста, не удаляйте ответ, это также может помочь людям разобраться в своих недоразумениях.
Мелисса