Bagaimana jQuery ditangguhkan dapat digunakan?


279

jQuery 1.5 membawa objek Tangguhan baru dan metode terlampir .when, .Deferreddan ._Deferred.

Bagi mereka yang belum pernah menggunakan .Deferredsebelumnya, saya telah menjelaskan sumbernya .

Apa saja kemungkinan penggunaan metode baru ini, bagaimana cara memasukkannya ke dalam pola?

Saya sudah membaca API dan sumbernya , jadi saya tahu apa fungsinya. Pertanyaan saya adalah bagaimana kita dapat menggunakan fitur-fitur baru ini dalam kode sehari-hari?

Saya punya contoh sederhana dari kelas buffer yang memanggil permintaan AJAX secara berurutan. (Berikutnya mulai setelah yang sebelumnya selesai).

/* Class: Buffer
 *  methods: append
 *
 *  Constructor: takes a function which will be the task handler to be called
 *
 *  .append appends a task to the buffer. Buffer will only call a task when the 
 *  previous task has finished
 */
var Buffer = function(handler) {
    var tasks = [];
    // empty resolved deferred object
    var deferred = $.when();

    // handle the next object
    function handleNextTask() {
        // if the current deferred task has resolved and there are more tasks
        if (deferred.isResolved() && tasks.length > 0) {
            // grab a task
            var task = tasks.shift();
            // set the deferred to be deferred returned from the handler
            deferred = handler(task);
            // if its not a deferred object then set it to be an empty deferred object
            if (!(deferred && deferred.promise)) {
                deferred = $.when();
            }
            // if we have tasks left then handle the next one when the current one 
            // is done.
            if (tasks.length > 0) {
                deferred.done(handleNextTask);
            }
        }
    }

    // appends a task.
    this.append = function(task) {
        // add to the array
        tasks.push(task);
        // handle the next task
        handleNextTask();
    };
};

Saya mencari demonstrasi dan kemungkinan penggunaan .Deferreddan .when.

Juga akan menyenangkan untuk melihat contoh ._Deferred.

Menautkan ke jQuery.ajaxsumber baru untuk contoh adalah curang.

Saya terutama tertarik pada teknik apa yang tersedia ketika kita abstrak apakah operasi dilakukan secara sinkron atau tidak sinkron.


19
Dari FAQ: hindari mengajukan pertanyaan subyektif di mana ... setiap jawaban sama-sama valid: "Apa bahasa Spanyol favorit Anda?" (Penekanannya)
TJ Crowder

2
@ TJCrowser Saya akan melihat penulisan ulang.
Raynos

5
Ini pertanyaan yang bagus tapi tidak mungkin ada yang banyak orang yang dapat menjawab :-)
runcing

2
@Pointy Saya terutama melihat orang-orang yang menggunakannya ketika itu adalah plugin pihak ke-3. Dan mendorong orang untuk duduk dan menggunakannya!
Raynos

1
._Deferredhanyalah "objek tangguhan" sejati yang .Deferredmenggunakan. Ini adalah objek internal yang kemungkinan besar tidak akan Anda butuhkan.
David Tang

Jawaban:


212

Kasus penggunaan terbaik yang bisa saya pikirkan adalah dalam caching tanggapan AJAX. Berikut adalah contoh yang dimodifikasi dari posting intro Rebecca Murphey tentang topik ini :

var cache = {};

function getData( val ){

    // return either the cached value or jqXHR object wrapped Promise
    return $.when(
        cache[ val ] || 
        $.ajax('/foo/', {
            data: { value: val },
            dataType: 'json',
            success: function( resp ){
                cache[ val ] = resp;
            }
        })
    );
}

getData('foo').then(function(resp){
    // do something with the response, which may
    // or may not have been retrieved using an
    // XHR request.
});

Pada dasarnya, jika nilai telah diminta satu kali sebelum dikembalikan segera dari cache. Jika tidak, permintaan AJAX mengambil data dan menambahkannya ke cache. The $.when/ .thentidak peduli tentang hal ini; yang perlu Anda khawatirkan adalah menggunakan respons, yang diteruskan ke .then()pawang dalam kedua kasus. jQuery.when()menangani yang tidak Janji / Ditangguhkan sebagai yang Selesai, segera mengeksekusi salah satu .done()atau .then()pada rantai.

Ditangguhkan sempurna untuk saat tugas mungkin atau tidak dapat beroperasi secara tidak sinkron, dan Anda ingin mengabstraksi kondisi itu dari kode.

Contoh dunia nyata lain menggunakan $.whenhelper:

$.when($.getJSON('/some/data/'), $.get('template.tpl')).then(function (data, tmpl) {

    $(tmpl) // create a jQuery object out of the template
    .tmpl(data) // compile it
    .appendTo("#target"); // insert it into the DOM

});

4
Dua contoh cemerlang. Saya mengimplementasikan sesuatu yang mirip dengan yang ke-2, tetapi dengan 4 permintaan ajax, dan itu berkinerja baik, selain jauh lebih mudah dibaca, kompak, logis, dapat dipelihara, dll. JQuery.Deferred adalah hal yang sangat bagus.
PJP

5
Berikut adalah video yang berguna tentang topik ini bigbinary.com/videos/3-using-deferred-in-jquery
Nick Vanderbilt

5
Caching tidak akan berfungsi jika hasilnya adalah nilai falsy. Juga saya tidak suka getData mengembalikan 2 tipe yang berbeda tergantung pada cabang yang diambil.
Marko Dumic

3
Lihat jawaban Julian D. di bawah ini untuk implementasi caching ajax yang lebih baik.
event_jr

1
Saya tidak mengerti bagaimana contoh kode pertama bahkan bekerja: Saya mengerti kasus di mana objek tidak di-cache, tetapi jika kemudian cache[ val ]TIDAK akan mengembalikan janji (dokumentasi jquery mengatakan bahwa parameternya adalah data yang dikembalikan oleh pengirim) yang berarti bahwa akses anggota .thenakan kesalahan ... kan? Apa yang saya lewatkan?
chacham15

79

Berikut ini adalah implementasi yang sedikit berbeda dari cache AJAX seperti pada jawaban ehynd .

Seperti disebutkan dalam pertanyaan tindak lanjut fortuneRice, implementasi ehynd sebenarnya tidak mencegah beberapa permintaan yang identik jika permintaan dilakukan sebelum salah satu dari mereka kembali. Itu adalah,

for (var i=0; i<3; i++) {
    getData("xxx");
}

kemungkinan besar akan menghasilkan 3 permintaan AJAX jika hasil untuk "xxx" belum di-cache sebelumnya.

Ini dapat diselesaikan dengan caching permintaan Tangguhan alih-alih hasilnya:

var cache = {};

function getData( val ){

    // Return a promise from the cache (if available)
    // or create a new one (a jqXHR object) and store it in the cache.
    var promise = cache[val];
    if (!promise) {
        promise = $.ajax('/foo/', {
            data: { value: val },
            dataType: 'json'
        });
        cache[val] = promise;
    }
    return promise;
}

$.when(getData('foo')).then(function(resp){
    // do something with the response, which may
    // or may not have been retreived using an
    // XHR request.
});

1
Saya pikir ini masih belum sempurna, karena Anda tidak pernah menghapus / memperbarui cache begitu pertama kali diambil. Ini akan membuat panggilan AJAX tidak berfungsi untuk pembaruan apa pun.
zyzyis

45

Ditangguhkan dapat digunakan sebagai pengganti mutex. Ini pada dasarnya sama dengan beberapa skenario penggunaan ajax.

MUTEX

var mutex = 2;

setTimeout(function() {
 callback();
}, 800);

setTimeout(function() {
 callback();
}, 500);

function callback() {
 if (--mutex === 0) {
  //run code
 }
}

TANGGUHAN

function timeout(x) {
 var dfd = jQuery.Deferred();
 setTimeout(function() {
  dfd.resolve();
 }, x);
 return dfd.promise();
}

jQuery.when(
timeout(800), timeout(500)).done(function() {
 // run code
});

Saat menggunakan Ditangguhkan sebagai mutex saja, perhatikan dampak kinerja (http://jsperf.com/deferred-vs-mutex/2). Meskipun kenyamanan, serta manfaat tambahan yang diberikan oleh Ditangguhkan sangat berharga, dan dalam penggunaan aktual (berdasarkan kejadian pengguna) dampak kinerja seharusnya tidak terlihat.


Sangat mengejutkan bagi saya untuk menemukan ini. Saya menggunakannya pada fungsi yang berisi setInterval yang akan mengembalikan janji terselesaikan dan hancur sendiri setelah lebar div membuatnya melebihi angka tertentu. Itu untuk pemecahan masalah dan solusi jika saya tidak bisa menyelesaikan masalah saya, tetapi saya sangat gembira karenanya.
JSG


20

Kegunaan lain yang saya maksudkan adalah mengambil data dari berbagai sumber. Dalam contoh di bawah ini, saya mengambil beberapa objek skema JSON independen yang digunakan dalam aplikasi yang ada untuk validasi antara klien dan server REST. Dalam hal ini, saya tidak ingin aplikasi sisi browser mulai memuat data sebelum semua skema dimuat. $ .when.apply (). then () sangat cocok untuk ini. Terima kasih kepada Raynos untuk petunjuk penggunaan kemudian (fn1, fn2) untuk memantau kondisi kesalahan.

fetch_sources = function (schema_urls) {
    var fetch_one = function (url) {
            return $.ajax({
                url: url,
                data: {},
                contentType: "application/json; charset=utf-8",
                dataType: "json",
            });
        }
    return $.map(schema_urls, fetch_one);
}

var promises = fetch_sources(data['schemas']);
$.when.apply(null, promises).then(

function () {
    var schemas = $.map(arguments, function (a) {
        return a[0]
    });
    start_application(schemas);
}, function () {
    console.log("FAIL", this, arguments);
});     

10

Contoh lain menggunakan Deferreds untuk menerapkan cache untuk segala jenis perhitungan (biasanya beberapa tugas yang intensif-kinerja atau berjalan lama):

var ResultsCache = function(computationFunction, cacheKeyGenerator) {
    this._cache = {};
    this._computationFunction = computationFunction;
    if (cacheKeyGenerator)
        this._cacheKeyGenerator = cacheKeyGenerator;
};

ResultsCache.prototype.compute = function() {
    // try to retrieve computation from cache
    var cacheKey = this._cacheKeyGenerator.apply(this, arguments);
    var promise = this._cache[cacheKey];

    // if not yet cached: start computation and store promise in cache 
    if (!promise) {
        var deferred = $.Deferred();
        promise = deferred.promise();
        this._cache[cacheKey] = promise;

        // perform the computation
        var args = Array.prototype.slice.call(arguments);
        args.push(deferred.resolve);
        this._computationFunction.apply(null, args);
    }

    return promise;
};

// Default cache key generator (works with Booleans, Strings, Numbers and Dates)
// You will need to create your own key generator if you work with Arrays etc.
ResultsCache.prototype._cacheKeyGenerator = function(args) {
    return Array.prototype.slice.call(arguments).join("|");
};

Berikut adalah contoh penggunaan kelas ini untuk melakukan beberapa perhitungan (simulasi berat):

// The addingMachine will add two numbers
var addingMachine = new ResultsCache(function(a, b, resultHandler) {
    console.log("Performing computation: adding " + a + " and " + b);
    // simulate rather long calculation time by using a 1s timeout
    setTimeout(function() {
        var result = a + b;
        resultHandler(result);
    }, 1000);
});

addingMachine.compute(2, 4).then(function(result) {
    console.log("result: " + result);
});

addingMachine.compute(1, 1).then(function(result) {
    console.log("result: " + result);
});

// cached result will be used
addingMachine.compute(2, 4).then(function(result) {
    console.log("result: " + result);
});

Tembolok yang mendasari yang sama dapat digunakan untuk tembolok permintaan Ajax:

var ajaxCache = new ResultsCache(function(id, resultHandler) {
    console.log("Performing Ajax request for id '" + id + "'");
    $.getJSON('http://jsfiddle.net/echo/jsonp/?callback=?', {value: id}, function(data) {
        resultHandler(data.value);
    });
});

ajaxCache.compute("anID").then(function(result) {
    console.log("result: " + result);
});

ajaxCache.compute("anotherID").then(function(result) {
    console.log("result: " + result);
});

// cached result will be used
ajaxCache.compute("anID").then(function(result) {
    console.log("result: " + result);
});

Anda dapat bermain dengan kode di atas di jsFiddle ini .


9

1) Gunakan untuk memastikan eksekusi callback yang dipesan:

var step1 = new Deferred();
var step2 = new Deferred().done(function() { return step1 });
var step3 = new Deferred().done(function() { return step2 });

step1.done(function() { alert("Step 1") });
step2.done(function() { alert("Step 2") });
step3.done(function() { alert("All done") });
//now the 3 alerts will also be fired in order of 1,2,3
//no matter which Deferred gets resolved first.

step2.resolve();
step3.resolve();
step1.resolve();

2) Gunakan untuk memverifikasi status aplikasi:

var loggedIn = logUserInNow(); //deferred
var databaseReady = openDatabaseNow(); //deferred

jQuery.when(loggedIn, databaseReady).then(function() {
  //do something
});

2

Anda dapat menggunakan objek yang ditangguhkan untuk membuat desain yang lancar yang bekerja dengan baik di browser webkit. Browser WebKit akan mem-api acara ukuran untuk setiap piksel jendela diubah ukurannya, tidak seperti FF dan IE yang mem-api acara hanya sekali untuk masing-masing ukuran. Akibatnya, Anda tidak memiliki kendali atas urutan fungsi yang terikat pada acara pengubahan ukuran jendela Anda. Sesuatu seperti ini menyelesaikan masalah:

var resizeQueue = new $.Deferred(); //new is optional but it sure is descriptive
resizeQueue.resolve();

function resizeAlgorithm() {
//some resize code here
}

$(window).resize(function() {
    resizeQueue.done(resizeAlgorithm);
});

Ini akan membuat serialisasi eksekusi kode Anda sehingga dieksekusi seperti yang Anda inginkan. Waspadalah terhadap jebakan saat meneruskan metode objek sebagai panggilan balik ke yang ditangguhkan. Setelah metode tersebut dieksekusi sebagai panggilan balik ke ditangguhkan, referensi 'ini' akan ditimpa dengan referensi ke objek yang ditangguhkan dan tidak akan lagi merujuk ke objek milik metode.


Bagaimana ini melakukan serialisasi? Anda telah menyelesaikan antrean sehingga resizeQueue.done(resizeAlgorithm)sama persis dengan resizeAlgorithm. Ini benar-benar palsu!
Raynos

Ketika kode Algoritma resize Anda kompleks, implementasi JavaScript di webkit akan kehilangan sinkronisasi ketika fungsi dipanggil untuk setiap piksel yang Anda ubah ukurannya jendela. Ditunda membuat panggilan balik Anda dalam antrian dan mengeksekusinya dalam urutan FIFO. Jadi, jika Anda menambahkan panggilan balik 'selesai' dan segera dieksekusi karena ditangguhkan sudah diselesaikan, panggilan balik 'selesai' lainnya yang ditambahkan ke ditangguhkan sementara panggilan balik pertama masih dijalankan akan ditambahkan ke antrian dan harus menunggu untuk panggilan balik pertama untuk kembali. Saya harap ini menjawab pertanyaan Anda.
Miloš Rašić

juru bahasa JS di browser adalah utas tunggal. Kecuali jika resizeAlgorithm Anda memiliki beberapa kode async di dalamnya, seluruh fungsi seharusnya sudah selesai beroperasi sebelum panggilan berikutnya .donedibuat.
Raynos

@ Raynos: Saya sadar akan hal itu, tetapi saya mencoba memanggil saja resizeAlgorithm di resize dan memberikan halaman putih kosong di browser webkit sambil bekerja dengan baik pada orang lain. Tangguhan memecahkan masalah ini. Saya belum punya cukup waktu untuk melakukan penelitian lebih dalam tentang ini. Mungkin berupa bug webkit. Saya tidak berpikir yang ditangguhkan seperti yang digunakan dalam contoh saya akan membantu jika resizeAlgorithm memiliki beberapa kode asinkron.
Miloš Rašić

2
Jika Anda tidak menggunakan sesuatu seperti throttle / debounce plugin benalman.com/projects/jquery-throttle-debounce-plugin untuk mencegah fungsi Anda menembak lebih banyak sekali per perubahan ukuran.
wheresrhys

2

Anda juga dapat mengintegrasikannya dengan perpustakaan pihak ketiga mana pun yang menggunakan JQuery.

Salah satu pustaka tersebut adalah Backbone, yang sebenarnya akan mendukung Deferred dalam versi berikutnya.


2
Menggunakan read more here di tempat on my blog. Ini adalah praktik yang lebih baik dan dapat menyelamatkan Anda menjawab dari (secara tidak sengaja) menjadi spam. :)
Lokesh Mehra

1

Saya baru saja menggunakan tangguhan dalam kode nyata. Dalam proyek jQuery Terminal saya memiliki fungsi exec yang memanggil perintah yang ditentukan oleh pengguna (seperti dia memasukkannya dan menekan enter), saya telah menambahkan Tangguhan ke API dan memanggil eksekutif dengan array. seperti ini:

terminal.exec('command').then(function() {
   terminal.echo('command finished');
});

atau

terminal.exec(['command 1', 'command 2', 'command 3']).then(function() {
   terminal.echo('all commands finished');
});

perintah dapat menjalankan kode async, dan eksekutif perlu memanggil kode pengguna secara berurutan. Api pertama saya menggunakan sepasang jeda / melanjutkan panggilan dan di API baru saya memanggil mereka otomatis ketika pengguna kembali berjanji. Jadi kode pengguna bisa digunakan

return $.get('/some/url');

atau

var d = new $.Deferred();
setTimeout(function() {
    d.resolve("Hello Deferred"); // resolve value will be echoed
}, 500);
return d.promise();

Saya menggunakan kode seperti ini:

exec: function(command, silent, deferred) {
    var d;
    if ($.isArray(command)) {
        return $.when.apply($, $.map(command, function(command) {
            return self.exec(command, silent);
        }));
    }
    // both commands executed here (resume will call Term::exec)
    if (paused) {
        // delay command multiple time
        d = deferred || new $.Deferred();
        dalyed_commands.push([command, silent, d]);
        return d.promise();
    } else {
        // commands may return promise from user code
        // it will resolve exec promise when user promise
        // is resolved
        var ret = commands(command, silent, true, deferred);
        if (!ret) {
            if (deferred) {
                deferred.resolve(self);
                return deferred.promise();
            } else {
                d = new $.Deferred();
                ret = d.promise();
                ret.resolve();
            }
        }
        return ret;
    }
},

dalyed_commands digunakan dalam fungsi resume yang memanggil exec lagi dengan semua dalyed_commands.

dan bagian dari fungsi perintah (saya telah melucuti bagian tidak terkait)

function commands(command, silent, exec, deferred) {

    var position = lines.length-1;
    // Call user interpreter function
    var result = interpreter.interpreter(command, self);
    // user code can return a promise
    if (result != undefined) {
        // new API - auto pause/resume when using promises
        self.pause();
        return $.when(result).then(function(result) {
            // don't echo result if user echo something
            if (result && position === lines.length-1) {
                display_object(result);
            }
            // resolve promise from exec. This will fire
            // code if used terminal::exec('command').then
            if (deferred) {
                deferred.resolve();
            }
            self.resume();
        });
    }
    // this is old API
    // if command call pause - wait until resume
    if (paused) {
        self.bind('resume.command', function() {
            // exec with resume/pause in user code
            if (deferred) {
                deferred.resolve();
            }
            self.unbind('resume.command');
        });
    } else {
        // this should not happen
        if (deferred) {
            deferred.resolve();
        }
    }
}

1

Jawaban oleh ehynds tidak akan berfungsi, karena cache data tanggapan. Seharusnya cache cache jqXHR yang juga merupakan Janji. Ini kode yang benar:

var cache = {};

function getData( val ){

    // return either the cached value or an
    // jqXHR object (which contains a promise)
    return cache[ val ] || $.ajax('/foo/', {
        data: { value: val },
        dataType: 'json',
        success: function(data, textStatus, jqXHR){
            cache[ val ] = jqXHR;
        }
    });
}

getData('foo').then(function(resp){
    // do something with the response, which may
    // or may not have been retreived using an
    // XHR request.
});

Jawaban oleh Julian D. akan bekerja dengan benar dan merupakan solusi yang lebih baik.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.