JQuery có thể được sử dụng như thế nào?


279

jQuery 1.5 mang đến cho các đối tượng thu nhập hoãn lại mới và các phương pháp kèm theo .when, .Deferred._Deferred.

Đối với những người chưa sử dụng .Deferredtrước đây, tôi đã chú thích nguồn cho nó .

Các ứng dụng có thể có của các phương pháp mới này là gì, làm thế nào để chúng ta sắp xếp chúng vào các mẫu?

Tôi đã đọc APInguồn , vì vậy tôi biết nó làm gì. Câu hỏi của tôi là làm thế nào chúng ta có thể sử dụng các tính năng mới này trong mã hàng ngày?

Tôi có một ví dụ đơn giản về một lớp đệm gọi yêu cầu AJAX theo thứ tự. (Tiếp theo bắt đầu sau khi kết thúc trước đó).

/* 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();
    };
};

Tôi đang tìm kiếm các cuộc biểu tình và có thể sử dụng .Deferred.when.

Nó cũng sẽ là đáng yêu để xem các ví dụ về ._Deferred.

Liên kết với jQuery.ajaxnguồn mới cho các ví dụ là gian lận.

Tôi đặc biệt quan tâm đến những kỹ thuật có sẵn khi chúng ta trừu tượng hóa liệu một hoạt động được thực hiện đồng bộ hay không đồng bộ.


19
Từ Câu hỏi thường gặp: tránh đặt câu hỏi chủ quan trong đó ... mọi câu trả lời đều có giá trị như nhau: Bạn thích gì ___? (nhấn mạnh của họ)
TJ Crowder

2
@TJCrowser Tôi sẽ xem xét viết lại nó.
Raynos

5
Đó là một câu hỏi hay nhưng không thể có nhiều người có thể trả lời :-)
chóp

2
@Pointy Tôi chủ yếu nhìn vào những người đã sử dụng nó khi nó là plugin của bên thứ 3. Và khuyến khích mọi người ngồi xuống và sử dụng nó!
Raynos

1
._Deferredchỉ đơn giản là "đối tượng hoãn lại" thực sự .Deferredsử dụng. Đó là một đối tượng nội bộ mà rất có thể bạn sẽ không bao giờ cần.
David Tang

Câu trả lời:


212

Trường hợp sử dụng tốt nhất tôi có thể nghĩ đến là trong bộ đệm phản hồi AJAX. Đây là một ví dụ sửa đổi từ bài giới thiệu của Rebecca Murphey về chủ đề :

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

Về cơ bản, nếu giá trị đã được yêu cầu một lần trước khi nó được trả về ngay lập tức từ bộ đệm. Mặt khác, một yêu cầu AJAX tìm nạp dữ liệu và thêm nó vào bộ đệm. Các $.when/ .thenkhông quan tâm đến bất kỳ điều này; tất cả những gì bạn cần quan tâm là sử dụng phản hồi, được chuyển đến bộ .then()xử lý trong cả hai trường hợp. jQuery.when()xử lý một Người không hứa hẹn / Trì hoãn như một Người đã hoàn thành, ngay lập tức thực hiện bất kỳ .done()hoặc .then()trên chuỗi.

Trì hoãn là hoàn hảo khi nhiệm vụ có thể hoặc không thể hoạt động không đồng bộ và bạn muốn trừu tượng hóa điều kiện đó ra khỏi mã.

Một ví dụ thực tế khác sử dụng trình $.whentrợ giúp:

$.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
Hai ví dụ xuất sắc. Tôi đã triển khai một cái gì đó tương tự như thứ 2, nhưng với 4 yêu cầu ajax và nó hoạt động tốt, ngoài ra còn dễ đọc hơn, nhỏ gọn, logic, có thể bảo trì, v.v. jQuery.Deferred là một điều thực sự tốt.
PJP

5
Đây là một video hữu ích về chủ đề này bigbinary.com/ideo/3-USE-deferred-in-jquery
Nick Vanderbilt

5
Bộ nhớ đệm sẽ không hoạt động nếu kết quả là giá trị giả. Ngoài ra tôi không thích thực tế getData trả về 2 loại khác nhau tùy thuộc vào chi nhánh được thực hiện.
Marko Dumic

3
Xem câu trả lời của Julian D. bên dưới để triển khai bộ nhớ đệm ajax tốt hơn.
event_jr

1
Tôi không hiểu ví dụ mã đầu tiên hoạt động như thế nào: Tôi hiểu trường hợp đối tượng không được lưu trong bộ nhớ cache, nhưng nếu không thì sẽ cache[ val ]không trả lại lời hứa (tài liệu jquery nói rằng tham số là dữ liệu được người gửi trả về) có nghĩa là quyền truy cập của thành viên .thensẽ bị lỗi ... phải không? Tôi đang thiếu gì?
chacham15

79

Đây là một cách thực hiện khác nhau của bộ đệm AJAX như trong câu trả lời của ehynd .

Như đã lưu ý trong câu hỏi tiếp theo của fortuneRice, việc triển khai ehynd thực sự không ngăn được nhiều yêu cầu giống hệt nhau nếu các yêu cầu được thực hiện trước khi một trong số chúng được trả lại. Đó là,

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

rất có thể sẽ dẫn đến 3 yêu cầu AJAX nếu kết quả cho "xxx" chưa được lưu trong bộ nhớ cache trước đó.

Điều này có thể được giải quyết bằng cách lưu vào bộ nhớ cache của yêu cầu thay vì kết quả:

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
Tôi nghĩ rằng điều này vẫn chưa hoàn hảo, vì bạn không bao giờ xóa / cập nhật bộ đệm một lần đầu tiên. Điều này sẽ làm cho cuộc gọi AJAX không hoạt động cho bất kỳ cập nhật nào.
zyzyis

45

Một hoãn lại có thể được sử dụng thay thế cho một mutex. Điều này về cơ bản giống như nhiều kịch bản sử dụng ajax.

MUTEX

var mutex = 2;

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

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

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

HOÃN LẠI

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

Khi chỉ sử dụng Trì hoãn dưới dạng mutex, hãy coi chừng các tác động về hiệu suất (http://jsperf.com/deferred-vs-mutex/2). Mặc dù sự tiện lợi, cũng như các lợi ích bổ sung được cung cấp bởi Trì hoãn rất đáng giá và trong thực tế sử dụng (dựa trên sự kiện do người dùng điều khiển), tác động hiệu suất không đáng chú ý.


Thật khó để tôi tìm thấy điều này. Tôi đã sử dụng nó trên một hàm chứa một setInterval sẽ trả về lời hứa đã giải quyết và tự hủy khi chiều rộng của div thực hiện trên một số nhất định. Đó là để khắc phục sự cố và giải pháp nếu tôi không thể giải quyết vấn đề của mình, nhưng tôi ngây ngất vì vấn đề đó.
JSG

28

Đây là một câu trả lời tự quảng cáo, nhưng tôi đã dành vài tháng để nghiên cứu vấn đề này và trình bày kết quả tại Hội nghị jQuery San Francisco 2012.

Đây là một video miễn phí của buổi nói chuyện:

https://www.youtube.com/watch?v=juRtEEsHI9E


20

Một cách sử dụng khác mà tôi đã đưa vào mục đích tốt là tìm nạp dữ liệu từ nhiều nguồn. Trong ví dụ dưới đây, tôi đang tìm nạp nhiều đối tượng lược đồ JSON độc lập được sử dụng trong một ứng dụng hiện có để xác thực giữa máy khách và máy chủ REST. Trong trường hợp này, tôi không muốn ứng dụng phía trình duyệt bắt đầu tải dữ liệu trước khi nó tải tất cả các lược đồ. $ .when.apply (). thì () là hoàn hảo cho việc này. Cảm ơn Raynos về con trỏ khi sử dụng sau đó (fn1, fn2) để theo dõi các điều kiện lỗi.

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

Một ví dụ khác sử dụng Deferreds để triển khai bộ đệm cho bất kỳ loại tính toán nào (thường là một số tác vụ chuyên sâu hoặc hiệu năng dài):

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

Dưới đây là một ví dụ về việc sử dụng lớp này để thực hiện một số phép tính (mô phỏng nặng):

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

Bộ đệm ẩn tương tự có thể được sử dụng để lưu trữ các yêu cầu 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);
});

Bạn có thể chơi với đoạn mã trên trong jsFiddle này .


9

1) Sử dụng nó để đảm bảo thực hiện lệnh gọi lại:

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) Sử dụng nó để xác minh trạng thái của ứng dụng:

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

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

2

Bạn có thể sử dụng một đối tượng hoãn lại để tạo ra một thiết kế trôi chảy, hoạt động tốt trong các trình duyệt webkit. Các trình duyệt Webkit sẽ kích hoạt thay đổi kích thước sự kiện cho từng pixel mà cửa sổ được thay đổi kích thước, không giống như FF và IE chỉ kích hoạt sự kiện một lần cho mỗi thay đổi kích thước. Do đó, bạn không có quyền kiểm soát thứ tự các hàm liên kết với sự kiện thay đổi kích thước cửa sổ của bạn sẽ thực thi. Một cái gì đó như thế này giải quyết vấn đề:

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

Điều này sẽ tuần tự hóa việc thực thi mã của bạn để nó thực thi như bạn dự định. Coi chừng những cạm bẫy khi truyền các phương thức đối tượng như gọi lại cho một hoãn lại. Khi phương thức đó được thực thi như một cuộc gọi lại để hoãn lại, tham chiếu 'this' sẽ được ghi đè bằng tham chiếu đến đối tượng bị trì hoãn và sẽ không còn tham chiếu đến đối tượng mà phương thức đó thuộc về.


Làm thế nào để làm bất kỳ serialization? Bạn đã giải quyết hàng đợi như vậy resizeQueue.done(resizeAlgorithm)là chính xác như resizeAlgorithm. Đó là một sự giả tạo hoàn toàn!
Raynos

Khi mã của thay đổi kích thước thuật toán của bạn phức tạp, việc triển khai JavaScript trong webkit sẽ mất đồng bộ hóa khi hàm được gọi cho mỗi pixel bạn thay đổi kích thước cửa sổ. Trì hoãn giữ các cuộc gọi lại của bạn trong một hàng đợi và thực hiện chúng theo thứ tự FIFO. Vì vậy, nếu bạn thêm một cuộc gọi lại 'xong' và nó thực thi ngay lập tức vì cuộc gọi bị hoãn đã được giải quyết, một cuộc gọi lại 'xong' khác được thêm vào cuộc gọi lại trong khi cuộc gọi lại đầu tiên vẫn đang thực hiện sẽ được thêm vào hàng đợi và sẽ phải chờ cho cuộc gọi lại đầu tiên để trở lại. Tôi mong bạn trả lời câu hỏi này.
Miloš Rašić

trình thông dịch JS trong trình duyệt là một luồng. Trừ khi resizeAlacticm của bạn có một số mã async bên trong nó, toàn bộ chức năng sẽ hoàn thành hoạt động trước khi .donethực hiện cuộc gọi tiếp theo .
Raynos

@Raynos: Tôi biết điều đó, nhưng tôi đã cố gắng gọi thay đổi kích thước thay đổi kích thước và nó cung cấp một trang trắng trong trình duyệt webkit trong khi hoạt động hoàn hảo trong các trình duyệt khác. Việc trì hoãn giải quyết vấn đề này. Tôi không có đủ thời gian để nghiên cứu sâu hơn về vấn đề này. Có thể là một lỗi webkit. Tôi không nghĩ rằng việc trì hoãn như được sử dụng trong ví dụ của tôi sẽ giúp ích nếu thay đổi kích thước thuật toán có một số mã không đồng bộ.
Miloš Rašić

2
Bạn không nên sử dụng một cái gì đó như plugin tiết kiệm / gỡ lỗi benalman.com/projects/jquery-thrption-debounce-plugin để ngăn các chức năng của bạn bắn nhiều hơn một lần mỗi lần thay đổi kích thước.
theo đó

2

Bạn cũng có thể tích hợp nó với bất kỳ thư viện bên thứ 3 nào sử dụng JQuery.

Một thư viện như vậy là Backbone, thực sự sẽ hỗ trợ Trì hoãn trong phiên bản tiếp theo của họ.


2
Sử dụng read more herethay thế on my blog. Đây là một cách thực hành tốt hơn và có thể giúp bạn tiết kiệm câu trả lời (vô tình) bị spam. :)
Lokesh Mehra

1

Tôi vừa mới sử dụng Trì hoãn trong mã thực. Trong dự án jQuery Terminal, tôi có hàm thực thi các lệnh gọi được xác định bởi người dùng (như anh ta đã nhập nó và nhấn enter), tôi đã thêm Trì hoãn vào API và gọi hàm thực thi bằng mảng. như thế này:

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

hoặc là

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

các lệnh có thể chạy mã async và exec cần gọi mã người dùng theo thứ tự. Api đầu tiên của tôi sử dụng cặp cuộc gọi tạm dừng / tiếp tục và trong API mới, tôi gọi những cuộc gọi đó tự động khi người dùng trả lại lời hứa. Vì vậy, mã người dùng chỉ có thể sử dụng

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

hoặc là

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

Tôi sử dụng mã như thế này:

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 được sử dụng trong chức năng tiếp tục gọi lại exec với tất cả dalyed_commands.

và một phần của chức năng lệnh (Tôi đã tước các phần không liên quan)

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

Câu trả lời của ehynds sẽ không hoạt động, vì nó lưu trữ dữ liệu phản hồi. Nó nên lưu trữ jqXHR cũng là một Promise. Đây là mã chính xác:

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

Câu trả lời của Julian D. sẽ hoạt động chính xác và là một giải pháp tốt hơn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.