jQuery відкладені та обіцянки - .then () vs .done ()


473

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

Чи хтось, будь ласка, просвітить мене до правильного використання?


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

@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.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
})

121
+1 для поняття, doneяке нічого не робить для результату, коли thenзмінюється результат. Величезний бал пропустили інші imo.
Шанімаль

9
Напевно, варто згадати, до якої версії jQuery це стосується, оскільки поведінка thenзмінилася в 1,8
bradley.ayers

4
+1 Прямо до точки. Я створив приклад для запуску, якщо хтось хоче побачити, які ланцюги із змішаними doneта thenрезультатами дзвінків.
Майкл Кропат

7
вищенаведений приклад також підкреслює, що "зроблено" працює над оригінальним об'єктом обіцянки, створеним спочатку, але "потім" повертає нову обіцянку.
Пулак Канті Бхаттачарія

2
Це стосується jQuery 1.8+. Старіші версії діють як doneприклад. Перейдіть thenдо pipeпопереднього рівня, щоб отримати thenповедінку 1.8+ .
Девід Харкнесс

57

.done() має лише один зворотний дзвінок, і це успішний зворотний дзвінок

.then() має як успіх, так і невдачі зворотних дзвінків

.fail() має лише один зворотний зворотній дзвінок

тож саме від вас залежить, що ви повинні зробити ... чи вам байдуже, чи вдасться вона чи не вдасться?


18
Ви не зазначаєте, що "тоді" виробляє ланцюги викликів. Дивіться відповідь Лу4.
олігофрен

Ваша відповідь - з 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);

відкладено.then ()

додає обробники для виклику, коли "Відкладено" вирішено, відхилено або все ще працює .

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

у вашій публікації не з’ясовано, як thenсебе вести, якщо не failнадається зворотній дзвінок, а саме - взагалі не фіксується failсправа
BM

Випадок невдачі викликає виняток, який може потрапити на верхній рівень програми. Виняток також можна побачити в консолі JavaScript.
Девід Спектор

10

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

Ключова різниця між зробленим / тоді в тому

  • .done() ЗАВЖДИ повертає ті самі значення Обіцяння / завершені, з яких він розпочався, незалежно від того, що ви робите чи що ви повертаєте.
  • .then() завжди повертає НОВУ Обіцянку, і ви відповідаєте за те, щоб контролювати те, на чому ця Обіця базується на тому, яку функцію ви її передали.

Перекладене з jQuery на рідну ES2015 Promises, .done()це як би реалізація структури "tap" навколо функції у ланцюзі Promise, завдяки чому вона, якщо ланцюг перебуває у стані "вирішення", передасть значення функції .. але результат цієї функції НЕ вплине на саму ланцюг.

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 and doneWrap для ведення журналів, а не потім. Це тому, що функції console.log насправді нічого не повертають. А що станеться, якщо ви перейдете. Тоді функцію, яка нічого не повертає?

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

Це буде журнал:

5

невизначений

Що трапилось? Коли я застосував. Тож початкове значення, з якого ми почали, в основному було втрачено.

.then()по суті є формою композиції функції: результат кожного кроку використовується як аргумент функції на наступному кроці. Ось чому .done найкраще розглядати як "дотик" -> це насправді не частина композиції, а лише те, що підкрадається погляд на значення на певному кроці і виконує функцію за цим значенням, але насправді не змінює композиція будь-яким способом.

Це досить принципова відмінність, і, ймовірно, є вагомою причиною того, що в "Обіцяннях" немає методу .done. Нам не потрібно вникати, чому немає методу .fail, тому що це ще складніше (а саме .fail / .catch НЕ є дзеркалами функцій .done / .then -> в .catch, які повертають голі значення не "Залишайтеся" відхиленими, як ті, що передаються. Потім вони вирішуються!)


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 як її результат.


"тоді () завжди означає, що він буде викликаний у будь-якому випадку" - неправда. тоді () ніколи не викликається у випадку помилки всередині Обіцяння.
Девід Спектор

Цікавий аспект того, що а jQuery.Deferred()може отримати декілька значень, які він належним чином передає на перше .then(). - Хоча трохи дивно ... як і будь-який наступний, .then()не можна цього зробити. (Обраний інтерфейс через returnможе повернути лише одне значення.) Народний Javascript Promiseне робить цього. (Що більше, якщо чесно.)
Роберт Сімер


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має, здається)

Btw, винятки з .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 () був доданий до відкладеного, який уже був вирішений або відхилений, зворотний виклик запускається негайно та синхронно.


-5

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

На практиці, якщо ви не плануєте додавати більше обіцянок до обіцянки, вам слід скористатися .done(). Детальніше дивіться, чому обіцянки потрібно виконувати


6
Обережно! Ця відповідь була б правильною для декількох реалізацій обіцянок, але не jQuery, в якій .done()немає виконуючої ролі. Документація говорить: "Оскільки deferred.done () повертає відкладений об'єкт, інші методи відкладеного об'єкта можуть бути прив'язані до цього, включаючи додаткові .done () методи". .fail()не згадується, але, так, це теж може бути прикуто.
Roamer-1888

1
Мій поганий, не перевірив jQuery
gleb bahmutov

1
@glebbahmutov - можливо, ви повинні видалити цю відповідь, щоб інших не плутати? Просто доброзичлива пропозиція :)
Андрій

2
Не видаляйте відповідь, це може допомогти людям також зрозуміти свої непорозуміння.
Меліса
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.