jQuery 지연을 어떻게 사용할 수 있습니까?


279

jQuery를 1.5은 새로운 이연 객체와 연결 방법을 제공합니다 .when, .Deferred._Deferred.

.Deferred이전에 사용하지 않은 사람들을 위해 소스에 주석을 달았 습니다 .

이 새로운 방법의 가능한 사용법은 무엇입니까? 패턴에 맞추는 방법은 무엇입니까?

API소스를 이미 읽었 으므로 그 기능을 알고 있습니다. 내 질문은 일상적인 코드에서 이러한 새로운 기능을 어떻게 사용할 수 있습니까?

AJAX 요청을 순서대로 호출하는 버퍼 클래스 의 간단한 예가 있습니다. (이전 단계가 끝나면 다음 단계가 시작됩니다).

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

나는 시위와의 가능한 사용을 찾고 있어요 .Deferred.when.

의 예제를 보는 것도 좋을 것입니다 ._Deferred.

jQuery.ajax예를 들어 새로운 소스에 연결하는 것은 부정 행위입니다.

작업이 동 기적으로 수행되는지 비동기 적으로 수행되는지 추상화 할 때 어떤 기술을 사용할 수 있는지에 특히 관심이 있습니다.


19
FAQ에서 : 모든 대답이 똑같이 유효한 주관적인 질문을 하지 마십시오 :“가장 좋아하는 ______? (그들의 강조)
TJ Crowder

2
@TJCrowser 나는 그것을 다시 말해 볼 것이다.
Raynos

5
좋은 질문이지만 대답 할 수 있는 많은 사람들 이있을 수는 없습니다 :-)
Pointy

2
@ Pointy 나는 주로 타사 플러그인 일 때 사용했던 사람들을 보았습니다. 사람들이 앉아서 사용하도록 격려하십시오!
Raynos

1
._Deferred사용하는 진정한 "지연된 객체"입니다 .Deferred. 가장 필요하지 않은 내부 객체입니다.
David Tang

답변:


212

내가 생각할 수있는 가장 좋은 사용 사례는 AJAX 응답을 캐싱하는 것입니다. 다음 은 주제에 대한 Rebecca Murphey의 소개 게시물 에서 수정 된 예입니다 .

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

기본적으로 값이 이미 한 번 요청 된 경우 캐시에서 즉시 반환됩니다. 그렇지 않으면 AJAX 요청이 데이터를 가져 와서 캐시에 추가합니다. $.when/는 .then이 중 하나에 대해 상관하지 않는다; .then()두 가지 경우 모두 처리기에 전달되는 응답을 사용하기 만하면됩니다. jQuery.when()비 약속 / 지연을 완료된 것으로 처리하여 즉시 .done()또는 .then()체인에서 실행합니다 .

지연은 작업이 비동기 적으로 작동하거나 작동하지 않을 때 적합하며 코드에서 해당 조건을 추상화하려고합니다.

$.when도우미 를 사용하는 또 다른 실제 예 :

$.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
두 개의 화려한 예. 나는 두 번째 것과 비슷한 것을 구현했지만 4 개의 아약스 요청으로 훨씬 더 읽기 쉽고 컴팩트하고 논리적이고 유지 관리 가능합니다 .jQuery.Deferred는 정말 좋습니다.
PJP

5
다음은이 주제에 대한 유용한 비디오입니다. bigbinary.com/videos/3-using-deferred-in-jquery
Nick Vanderbilt

5
결과가 잘못된 값이면 캐싱이 작동하지 않습니다. 또한 getData가 가져온 분기에 따라 2 가지 유형을 반환한다는 사실이 마음에 들지 않습니다.
Marko Dumic

3
아약스 캐싱을 더 잘 구현하려면 아래 Julian D.의 답변을 참조하십시오.
event_jr

1
첫 번째 코드 예제가 어떻게 작동하는지 이해하지 못합니다. 객체가 캐시되지 않은 경우를 이해하지만 cache[ val ]약속 이 없으면 약속을 반환 하지 않습니다 (jquery 설명서에는 매개 변수가 발신자가 반환 한 데이터라고 나타냄). 의 회원 액세스 .then오류 ... 맞아? 내가 무엇을 놓치고 있습니까?
chacham15

79

다음은 ehynd 's answer 에서와 같이 AJAX 캐시를 약간 다르게 구현 한 입니다.

fortuneRice의 후속 질문 에서 언급했듯이 ehynd의 구현은 요청 중 하나가 반환되기 전에 요청이 수행 된 경우 실제로 동일한 요청을 여러 번 방지하지 못했습니다. 그건,

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

"xxx"에 대한 결과가 이전에 캐시되지 않은 경우 3 개의 AJAX 요청이 발생합니다.

결과 대신 요청의 지연을 캐싱하여 해결할 수 있습니다.

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
처음 가져온 후에는 캐시를 지우거나 업데이트하지 않기 때문에 이것이 여전히 완벽하지 않다고 생각합니다. 이렇게하면 AJAX 호출이 업데이트에서 작동하지 않게됩니다.
zyzyis

45

뮤텍스 대신에 지연을 사용할 수 있습니다. 이것은 본질적으로 여러 ajax 사용 시나리오와 동일합니다.

뮤텍스

var mutex = 2;

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

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

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

지연됨

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

Deferred를 뮤텍스로만 사용하는 경우 성능 영향에주의하십시오 (http://jsperf.com/deferred-vs-mutex/2). Deferred가 제공하는 편의성과 추가 혜택은 그만한 가치가 있지만 실제 (사용자 중심 이벤트 기반) 사용에서는 성능 영향이 눈에 띄지 않아야합니다.


내가 이것을 찾는 것은 놀랍게도 어려웠다. 나는 div의 너비가 특정 숫자를 초과하면 해결 된 약속을 반환하고 자체 파괴되는 setInterval을 포함하는 함수에서 사용했습니다. 문제를 해결할 수없는 경우 문제 해결 및 해결을위한 것이었지만 황홀한 것입니다.
JSG


20

내가 좋은 목적으로 사용했던 또 다른 용도는 여러 소스에서 데이터를 가져 오는 것입니다. 아래 예에서는 클라이언트와 REST 서버 간의 유효성 검사를 위해 기존 애플리케이션에서 사용되는 여러 개의 독립적 인 JSON 스키마 객체를 가져옵니다. 이 경우 브라우저 측 응용 프로그램이 모든 스키마를로드하기 전에 데이터로드를 시작하지 않으려합니다. $ .when.apply (). then ()은 이것에 완벽합니다. then (fn1, fn2)을 사용하여 오류 조건을 모니터링하는 방법에 대한 포인터는 Raynos에게 감사하십시오.

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

Deferreds를 사용하여 모든 종류의 계산 (일반적으로 성능 집약적이거나 오래 실행되는 작업)에 대한 캐시를 구현하는 또 다른 예 :

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

다음은이 클래스를 사용하여 시뮬레이션 된 무거운 계산을 수행하는 예입니다.

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

동일한 기본 캐시를 사용하여 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);
});

이 jsFiddle 에서 위의 코드로 재생할 수 있습니다 .


9

1) 콜백을 순서대로 실행하려면 사용하십시오.

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) 앱 상태를 확인하는 데 사용하십시오.

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

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

2

지연된 객체를 사용하여 웹킷 브라우저에서 잘 작동하는 유동적 인 디자인을 만들 수 있습니다. 웹킷 브라우저는 각 크기 조정에 대해 한 번만 이벤트를 발생시키는 FF 및 IE와 달리 창의 크기가 조정되는 각 픽셀에 대해 크기 조정 이벤트를 발생시킵니다. 결과적으로, 창 크기 조정 이벤트에 바인드 된 함수가 실행될 순서를 제어 할 수 없습니다. 이와 같은 것이 문제를 해결합니다.

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

그러면 코드 실행이 직렬화되어 의도 한대로 실행됩니다. 객체 메소드를 콜백으로 연기 할 때 함정에주의하십시오. 이러한 메소드가 지연된 콜백으로 실행되면 지연된 오브젝트를 참조하여 'this'참조가 겹쳐 쓰여지고 메소드가 속한 오브젝트를 더 이상 참조하지 않습니다.


이것이 어떻게 직렬화를 수행합니까? 이미 대기열을 해결 했으므로 resizeQueue.done(resizeAlgorithm)와 정확히 동일합니다 resizeAlgorithm. 완전 가짜입니다!
Raynos

resizeAlgorithm의 코드가 복잡한 경우, 창 크기를 조정하는 각 픽셀에 대해 함수가 호출되면 웹킷의 JavaScript 구현에서 동기화가 느슨해집니다. 지연은 콜백을 대기열에 보관하고 FIFO 순서로 실행합니다. 따라서 '완료'콜백을 추가하고 지연된 지연이 이미 해결되어 즉시 실행되는 경우 첫 번째 콜백이 여전히 실행되는 동안 지연된 추가 된 다른 '완료'콜백이 대기열에 추가되어 대기해야합니다. 첫 번째 콜백을 반환합니다. 이것이 귀하의 질문에 답변되기를 바랍니다.
Miloš Rašić

브라우저의 JS 인터프리터는 단일 스레드입니다. resizeAlgorithm에 비동기 코드가 포함되어 있지 않으면 다음에 호출하기 전에 전체 함수 작동이 완료되어야합니다 .done.
Raynos

@ Raynos : 나는 그것을 알고 있지만 resizeAlgorithm을 resizeAlgorithm에서 resizeAlgorithm을 호출하려고 시도했지만 다른 웹 사이트에서 완벽하게 작업하면서 웹킷 브라우저에서 빈 흰색 페이지를 제공합니다. 연기 된 사람은이 문제를 해결합니다. 이것에 대해 더 깊이 연구 할 시간이 없었습니다. 웹킷 버그 일 수 있습니다. resizeAlgorithm에 비동기 코드가있는 경우 내 예제에 사용 된 지연이 도움이 될 것이라고 생각하지 않습니다.
Miloš Rašić

2
throttle / debounce 플러그인 benalman.com/projects/jquery-throttle-debounce-plugin 과 같은 것을 사용하여 크기 조정 당 한 번 더 많은 기능이 실행 되는 것을 방지해야합니다.
wheresrhys

2

JQuery를 사용하는 타사 라이브러리와 통합 할 수도 있습니다.

그러한 라이브러리 중 하나는 Backbone이며, 실제로 다음 버전에서 Deferred를 지원합니다.


2
read more here대신에 사용하십시오 on my blog. 이 방법을 사용하는 것이 더 좋으며 스팸 메일로부터 실수로 답변을 구할 수 있습니다. :)
Lokesh Mehra

1

방금 실제 코드에서 Deferred를 사용했습니다. 프로젝트에서 jQuery 터미널 에서 사용자가 정의한 명령을 호출하는 함수 exec (예 : 입력하고 Enter 키를 누름)를 API에 Deferreds를 추가하고 배열로 exec를 호출했습니다. 이처럼 :

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

또는

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

명령은 비동기 코드를 실행할 수 있으며 exec는 사용자 코드를 순서대로 호출해야합니다. 내 첫 API는 일시 중지 / 재개 호출 쌍을 사용하고 새로운 API에서는 사용자가 약속을 반환 할 때 자동으로 호출합니다. 따라서 사용자 코드는

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

또는

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

나는 다음과 같은 코드를 사용한다 :

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는 모든 dalyed_commands와 함께 exec를 다시 호출하는 이력서 기능에 사용됩니다.

명령 기능의 일부 (관련 부품을 제거하지 않았습니다)

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

ehynds의 답변은 응답 데이터를 캐시하기 때문에 작동하지 않습니다. 또한 약속 인 jqXHR을 캐시해야합니다. 올바른 코드는 다음과 같습니다.

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

Julian D.의 답변이 올바르게 작동하고 더 나은 솔루션입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.