Underscore: Saran: _.debounce dan _.throttle ambil parameter tambahan untuk cara menggabungkan argumen

Dibuat pada 22 Sep 2011  ·  11Komentar  ·  Sumber: jashkenas/underscore

Jika saya menggunakan _.debounce() untuk membuat fungsi yang di-debounce dan kemudian memanggilnya 3 kali berturut-turut dengan 3 set argumen yang berbeda, maka (mulai v1.1.7) fungsi payload yang dibungkus akhirnya akan dipanggil dengan argumen yang ditentukan oleh Panggilan ke-3 - itu adalah argumen pertama dan kedua dibuang.

Meskipun ini sering valid (dan apa yang biasanya dilakukan oleh debounce kunci, karenanya merupakan default yang masuk akal) saya mendapati diri saya ingin menggunakan debounce untuk mengumpulkan argumen, misalnya saya memiliki panggilan AJAX yang bisa mendapatkan beberapa kunci sekaligus, jadi saya menggunakan debounce untuk buffer tombol up sebentar dan kemudian mengeluarkan permintaan gabungan.

Oleh karena itu saran saya adalah bahwa debounce mengambil argumen "gabungkan" ke-3 opsional yang akan dipanggil dengan 2 argumen,

  • yang pertama adalah daftar argumen yang terakumulasi sejauh ini (mungkin tidak terdefinisi - yaitu tidak ada daftar pada panggilan pertama)
  • yang ke-2 adalah daftar argumen untuk panggilan terbaru (mungkin daftar kosong)
    dan mengembalikan daftar baru dari akumulasi argumen. Ketika fungsi payload dipanggil, daftar akumulasi argumen dihapus.

Jika tidak ada nilai yang diteruskan untuk parameter kombinasi, kombinasi default mempertahankan perilaku yang ada
function(acc, newargs) { return newargs; }
tetapi Anda juga dapat memutuskan untuk menggunakan set argumen pertama
function(acc, newargs) { return acc || newargs; }
atau apa yang ingin saya lakukan hanyalah menambahkan semua argumen
function(acc,newargs) { return (acc || []).concat(newargs); }
dan tentu saja orang lain mungkin ingin melakukan sesuatu yang lebih menarik

Ini akan membutuhkan perubahan berikut pada fungsi batas internal:

  // Internal function used to implement `_.throttle` and `_.debounce`.
  var limit = function(func, wait, debounce, combine) {
    var timeout, allargs;
    return function() {
      var context = this;
      allargs = combine(allargs,  slice.call(arguments,0))
      var throttler = function() {
        timeout = null;
        var args = allargs;
        allargs = undefined;
        func.apply(context, args);
      };
      if (debounce) clearTimeout(timeout);
      if (debounce || !timeout) timeout = setTimeout(throttler, wait);
    };
  };

dan kemudian perubahan ke debounce untuk menerima dan meneruskan argumen baru dengan nilai default jika tidak ditentukan.

  _.debounce = function(func, wait, combine) {
    return limit(func, wait, true, combine || function(acc,newargs) { return newargs; });
  };

Fungsi throttle yang sesuai saat ini menggunakan set argumen pertama saja (throttle secara efektif mengabaikan panggilan yang terjadi dalam menunggu milidetik dari panggilan pertama dan menggunakan set argumen panggilan pertama, debounce secara efektif mengabaikan semua kecuali panggilan terakhir dalam urutan yang terjadi dalam periode tunggu satu sama lain), jadi saya sarankan di bawah ini untuk kembali mempertahankan perilaku default saat ini

  _.throttle = function(func, wait, combine) {
    return limit(func, wait, false, combine || function(acc,newargs) { return acc || newargs; });
  };

Tampaknya ini cara termudah dan paling umum untuk mencapai fungsi ini tanpa pembungkus berlebihan untuk mempertahankan daftar argumen, tetapi saya tertarik untuk mengetahui apakah ada cara mudah untuk mencapai ini tanpa mengubah garis bawah.

enhancement wontfix

Komentar yang paling membantu

OK, bukan untuk terus menggedor, tetapi jika ada yang melihat ini nanti dan bertanya-tanya bagaimana melakukan hal yang sama, saya pikir ini adalah cara terbersih tanpa memodifikasi debounce itu sendiri (saya menambahkannya ke objek _, orang lain mungkin lebih suka tidak ke)

_.mixin({
  debounceReduce: function(func, wait, combine) {
    var allargs,
        context,
        wrapper = _.debounce(function() {
            var args = allargs;
            allargs = undefined;
            func.apply(context, args);
        }, wait);
        return function() {
            context = this;
            allargs = combine.apply(context, [allargs,  Array.prototype.slice.call(arguments,0)]);
            wrapper();
        };
    }
})

Ini memberikan fungsi debouncing yang argumennya dikurangi dengan fungsi kombinasikan, misalnya,

  delayLog = _.debounceReduce(function() { console.log(arguments); }, 5000, 
                              function(acc,args) { return (acc || []).concat(args); });
  delayLog(3,4);
  delayLog(7,8,9);

akan beberapa detik kemudian memanggil console.log dengan array [3,4,7,8,9]

Semua 11 komentar

Mengomentari saran saya sendiri, panggilan ke combine() harus menentukan konteks yang sama dengan fungsi payload, karenanya

allargs = combine.apply(this, [allargs, slice.call(arguments,0)])

jika argumen perlu mengakses objek konteks ....

Sekarang harus diperbaiki pada master. throttle harus menunjukkan perilaku yang benar di mana ia selalu menggunakan salinan terbaru dari argumen Anda, langsung aktif sekali, dan setiap N detik setelahnya ... dan me-reset sendiri N detik setelahnya pemicu trailing terakhir telah terjadi.

Saya pikir komentar dekat Anda berlaku untuk masalah lain (mungkin #170), karena masalah yang diangkat oleh permintaan ini masih berlaku pada master.
Masih tidak ada cara mudah untuk mendebounce atau mencekik argumen akumulasi dari panggilan yang sedang digabungkan, dan saya masih berpikir itu adalah tambahan opsional yang berguna yang membuat perilaku default tidak berubah ketika argumen penggabungan opsional tidak ditentukan.

Anda benar. Mengumpulkan argumen berada di luar cakupan Garis Bawah -- jangan ragu untuk menyimpan data yang terkumpul di tempat yang baik di luar fungsi _.throttle dan _.debounce .

Sayang sekali, saya menganggap debounce sebagai semacam flip-kiri (mengurangi) lebih dari beberapa panggilan dengan batas waktu maka akumulator ... tapi itu panggilan Anda :)

OK, bukan untuk terus menggedor, tetapi jika ada yang melihat ini nanti dan bertanya-tanya bagaimana melakukan hal yang sama, saya pikir ini adalah cara terbersih tanpa memodifikasi debounce itu sendiri (saya menambahkannya ke objek _, orang lain mungkin lebih suka tidak ke)

_.mixin({
  debounceReduce: function(func, wait, combine) {
    var allargs,
        context,
        wrapper = _.debounce(function() {
            var args = allargs;
            allargs = undefined;
            func.apply(context, args);
        }, wait);
        return function() {
            context = this;
            allargs = combine.apply(context, [allargs,  Array.prototype.slice.call(arguments,0)]);
            wrapper();
        };
    }
})

Ini memberikan fungsi debouncing yang argumennya dikurangi dengan fungsi kombinasikan, misalnya,

  delayLog = _.debounceReduce(function() { console.log(arguments); }, 5000, 
                              function(acc,args) { return (acc || []).concat(args); });
  delayLog(3,4);
  delayLog(7,8,9);

akan beberapa detik kemudian memanggil console.log dengan array [3,4,7,8,9]

@schmerg — ini terlihat sangat berguna. Apakah Anda bersedia melisensikan kode itu di bawah lisensi MIT? (Jawaban "ya" sudah cukup!)

@markjaquith Tentu saja - ya. Senang...

Jika ada yang datang dan ingin memperbarui/mengkomentari versi js modern di atas:

_.mixin({
  debounceReduce(func, wait, combine) {
    let allArgs; // accumulator for args across calls

    // normally-debounced fn that we will call later with the accumulated args
    const wrapper = _.debounce(() => func(allArgs), wait);

    // what we actually return is this function which will really just add the new args to
    // allArgs using the combine fn
    return (...args) => {
      allArgs = combine(allArgs,  [...args]);
      wrapper();
    };
  },
});

@kmannislands Hei, versi Anda tidak mengatur ulang allArgs di dalam wrapper() , jadi panggilan berikutnya ke func() mendapatkan kumpulan argumen historis serta kumpulan saat ini.

Bukankah seharusnya:

const wrapper = _.debounce(() => {
    const args = allArgs;
    allArgs = undefined;
    func(args);
}, wait);

@markjaquith +1

Juga func( args ) berbeda dari versi asli yang menggunakan func.apply(context, args); .
Untuk yang pertama, args digunakan seperti pada target func() , sedangkan di _(kode asli)_ selanjutnya Anda perlu menggunakan arguments dalam fungsi normal atau ( ...args ) dalam fungsi panah gemuk es6.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat