1. 서문
콜백의 지옥에서 프런트 엔드를 천국으로 되돌리기 위해 jQuery는 Promise 개념도 도입했습니다. Promise는 코드의 비동기 동작을 더욱 우아하게 만드는 추상화입니다. Promise를 사용하면 동기 코드를 작성하는 것처럼 비동기 코드를 작성할 수 있습니다. jQuery는 버전 1.5부터 CommonJS Promise/A 사양의 헤비급 솔루션을 구현했지만 사양에 따라 엄격하게 구현되지 않았으며 일부 API 차이가 있습니다.
자, 기능을 살펴보겠습니다(이 글의 예는 jquery 버전 1.8 이상을 기반으로 합니다).
2. 예시
과거에는 애니메이션을 작성할 때 보통 다음과 같이 했습니다.
$('.animateEle').animate({ opacity:'.5' }, 4000,function(){ $('.animateEle2').animate({ width:'100px' },2000,function(){ // 这样太伤了 $('.animateEle3').animate({ height:'0' },2000); }); });
콜백을 이렇게 사용하면 너무 해로울 것입니다. 다행히도 이 문제를 우아하게 해결하는 몇 가지 기본 제공되는 Promise 솔루션이 있습니다.
jQuery에서 제공하는 솔루션을 살펴보겠습니다.
var animate1 = function() { return $('.animateEle1').animate({opacity:'.5'},4000).promise(); }; var animate2 = function() { return $('.animateEle2').animate({width:'100px'},2000).promise(); }; var animate3 = function(){ return $('.animateEle3').animate({height:'0'},2000).promise(); }; // so easy,有木有,so clear,有木有 $.when(animate1()).then(animate2).then(animate3);
분명히 변경된 코드가 이해하고 읽기 더 쉽습니다.
그러나 위 코드에서는 일부 세부 사항이 공개되지 않습니다. 조심하지 않으면 실수하기 쉽고 원하는 순서대로 애니메이션이 완성되는 효과를 얻을 수 없습니다. jQuery에서 제공하는 Promise 객체와 Deferred 객체의 메소드를 완벽히 이해하고 어떻게 사용하는지 살펴보자.
3. Promise 및 지연 객체 메서드
Promise 개체는 실제로 지연된 개체의 특별한 경우입니다. 왜냐하면 Promise 개체는 비동기 상태를 변경할 수 없지만 지연된 개체는 변경할 수 있기 때문입니다. 이는 분석법 설계에 명확하게 반영됩니다.
1.promise 객체 방식
일반적으로 DOM, 애니메이션, Ajax 관련 메소드에는 promise 메소드를 사용할 수 있습니다. promise 메소드를 호출하면 promise 객체가 반환됩니다. Promise 메소드는 체인에서 호출될 수 있습니다.
Promise 개체에는 세 가지 일반적인 메서드(완료, 실패, then)가 있습니다.
jquery에는 인터페이스 메소드가 너무 많아서 초기 이벤트 메소드 바인딩과 마찬가지로 결국에는 모두 분류되지 않습니다. 온이 그걸 처리하러 왔나요?
다음과 같은 코드 예시:
(1) DOM은 약속 방식을 사용합니다:
var box=$('#box'); box.promise().done(function(ele){ console.log(ele);//jQuery box });
(2) Ajax는 promise 메소드를 사용합니다(기본적으로 promise 객체를 반환하므로 promise 메소드를 명시적으로 호출할 필요가 없습니다).
$.post('/',{}).done(function(data){ console.log('请求成功'); }).fail(function(){ console.log('请求错误'); });
애니메이션 예시는 이미 존재하므로 다시 나열하지 않겠습니다.
2.지연 객체 방식
그럼 Deferred와 Promise의 차이점은 무엇인가요? 앞서 본 것처럼 Promise는 비동기 함수가 반환하는 객체입니다. 이러한 함수를 직접 작성하려면 deferred를 사용해야 합니다.
지연된 객체는 promise 객체와 동일한 작업을 수행할 수 있지만 done() 및 failure() 함수를 트리거하는 두 가지 함수가 있습니다.
연기된 객체에는 성공적인 결과를 처리하고 done()과 관련된 함수를 실행하는 해결() 함수가 있습니다. 거절() 함수는 실패한 결과를 처리하고 실패()와 관련된 함수를 실행하는 데 사용됩니다.
Resolve() 및 Reject() 함수 모두에 매개변수를 제공할 수 있으며, 그러면 둘 다 done() 및 실패()와 관련된 콜백 함수에 전달됩니다.
Promise 객체에는 Resolve() 및 Reject() 함수가 없습니다. 이는 Promise를 다른 스크립트에 넣었고 Promise가 Promise를 해결하거나 거부하는 것을 원하지 않기 때문입니다.
다음은 deferred의 간단한 예시입니다. html은 id 속성이 "result"인 단순한 빈 div입니다.
$('#result').html('waiting...'); var promise = wait(); promise.done(result); function result() { $('#result').html('done'); } function wait() { var deferred = $.Deferred(); setTimeout(function() { deferred.resolve(); }, 2000); return deferred.promise(); }
그 중 wait() 함수는 Promise를 반환합니다. 2초 후에 구문 분석됩니다. setTimeout 외에도 애니메이션, 웹 작업자 등 비동기 함수의 모든 항목을 이러한 방식으로 사용할 수 있습니다. wait() 함수의 코드는 명확해야 합니다. 지연된 객체를 사용하지만 제한된 Promise 객체를 반환합니다.
지연된 객체, 즉 $.Deferred() 메소드와 $.when() 메소드를 사용하여 생성된 객체의 경우 일반적으로 사용되는 메소드는 다음과 같습니다.
resolve , reject , notify ; done , fail , progress ;
약속, 그때, 항상 방법도 있습니다.
이렇게 형식을 지정하는 이유는 해당 메서드가 대응하기 때문입니다. 즉, 해결 메서드는 완료 콜백 실행을 트리거하고, 거부는 실패 콜백을 트리거하며, 알림은 진행 콜백을 트리거합니다.
코드 직접 보기:
var wait = function(ms) { var dtd = $.Deferred(); setTimeout(dtd.resolve, ms); // setTimeout(dtd.reject, ms); // setTimeout(dtd.notify, ms); return dtd.promise(); //此处也可以直接返回dtd }; wait(2500).done(function() { console.log('haha,师太,你可让老衲久等了'); }).fail(function() { console.log('失败了'); }).progress(function(res) { console.log('等待中...'); });
我们看到了,上面的代码中,在 wait 函数中,返回的是个 promise 对象,而不是 deferred 对象。
要知道, promise 对象是没有 resolve , reject , notify 等方法的,也就意味着,你无法针对 promise 对象进行状态更改,只能在 done 或 fail 中进行回调配置。所以,你如果这么调用 wait(2500).resolve() 将会报错,因为 wait(2500) 返回的是个 promise 对象,不存在 resolve 方法。
但是,这么做,有个好处,我们把 dtd 这个 deferred 对象放在了 wai t函数中,作为了局部变量,避免了全局的污染;进一步通过 promise 方法,转化 dtd 这个 deferred 对象为 promise 对象,避免了函数 wait 外部可能发生的状态更改(假如我们确实有这个需求)。
比如:
var wait = function(ms) { var dtd = $.Deferred(); setTimeout(dtd.resolve, ms); // setTimeout(dtd.reject, ms); // setTimeout(dtd.notify, ms); return dtd; //此处也可以直接返回dtd }; wait(2500).reject().fail(function(){ console.log('失败了...............'); });
我们在外部更改了 wait 返回的 deferred 对象的状态,这样必然触发该对象的 fail 回调函数。
对于 always 方法,从字面意思上就很容易理解, deferred 对象无论是 resolve 还是 reject ,都会触发该方法的回调。
3.其它共性
此处讲讲 then 和 $.when 方法的使用。它们对 promise 对象也适用。
$.when 方法接受多个 deferred 对象或者纯javascript对象,返回 promise 对象。
then 方法依次接受三个回调,分别为 deferred 对象 resolve , reject , notify 后触发的回调,返回一个 promise 对象。注意,必须传入函数,而该函数只有返回一个 promise 对象,才能够让异步事件按照预期顺序来执行。
我们来看看最开始的动画示例代码, $.when(animate1()).then(animate2).then(animate3) , $.when 方法中接受了一个 animate1 的函数执行结果,也就是得到了一个 promise 对象,而后的 then 中,则只是接受了一个变量名,这样得到的结果是一个匿名的函数体,而该函数中返回的是 promise 对象。正好符合了我们对 then 接受参数的要求。
假如我们把执行语句改成: $.when(animate1()).then(animate2()).then(animate3()) ,这样造成的结果就是三个动画同步执行了。与 $.when(animate1(),animate2(),animate3()) 无异。
既然 then 是如此要求,那么与 then 方法类似的 done , fail , progress 也是一样的。