본문으로 건너뛰기

Promise 완전히 이해하기

무료2015-11-15#JS#Promise#Promise A+#Promise a plus#js promise#jquery promise#JavaScript Promise#Promise教程#Promise怎么用

경외에서 의심으로, 추측에서恍然大悟로, "뭐 -咦-难道 -哦~~", 본 문은 Promise 의 내부 메커니즘을 상세히 설명합니다

서두

단어에 해당하는 번역에 주의하여 전체 패턴 이해에 도움이 되도록 합니다

  • promise: 약속

  • pending:待定

  • settled:定了

  • resolve(d): (已)해결

  • reject(ed): (已)거부

  • onFulfilled: 이행됨 (이벤트)

  • onRejected: 거부됨 (이벤트)

  • then: 그 다음, 이어서

  • catch: 캡처

  • race: 경주

  • future: 미래 (값)

  • thenable: then 가능 (then 속성 보유)

一.Promise 란 무엇인가

비동기 작업 처리 메커니즘의 한 세트, Promise A+ 는 규격이며, jQuery, ES2015, q 의 promise 는 모두 이 규격의 한 구현입니다

二.Promise 의 특징

  • 비동기 작업의 실행 순서를 더 쉽게 제어 가능

  • 일련의 비동기 작업에서 오류를 유연하게 처리 가능

  • 체인 호출을 지원하며, 작성하기 좋음

  • 콜백 피라미드를 제거할 수 있으며, 보기 좋음

三.Promise 의 용도

  • 직렬 작업 큐 구현

  • 직렬 작업 파이프라인 구현

  • 런타임에야 실행 순서가 결정되는 직렬 작업 처리

(분량 제한으로, 구체적인 적용 장면은 나중에 전개)

四.Promise 타입

내부 속성 [[PromiseStatus]]/[[PromiseValue]]

####[[PromiseStatus]]: pending(待定) | resolved(已해결) | rejected(已거부)

뒤의 두 개를 총칭하여 settled(定了), pending 은 초기 상태로, new Promise(func) 후, func 중의 resolvereject 가 아직 실행되지 않은 경우, 이때 status=pending

####[[PromiseValue]]: undefined | resolve/reject 의 매개변수 | Promise.resolve/reject 의!thenable 매개변수 | onFulfilled/onRejected 의 반환값

then 이 호출될 때, 이 값은 thenonFulfilled 또는 onRejected 에 전달되며, resolvereject 가始終 실행되지 않으면, thenonFulfilledonRejected 도始終 실행되지 않습니다

생성자 매개변수 resolve/reject

####resolve(해결)

resolve 실행 후, thenonFulfilled 콜백을 트리거하고, resolve 의 매개변수를 onFulfilled 에 전달합니다

####reject(거부)

reject 실행 후, then | catchonRejected 콜백을 트리거하고, reject 의 매개변수를 onRejected 에 전달합니다

주의:바로 뒤의 thenonRejected 콜백을 선언하지 않은 경우, 다음 then | catchonRejected 에 전달되고, 아직 없으면, 계속 전달...끝까지 없으면, 오류 Uncaught (in promise) Error:xxx 보고

特殊的:resolve 의 매개변수가 Promise 객체인 경우, 해당 객체의 최종 [[PromiseValue]] 는 외층 Promise 객체 후속의 thenonFulfilled/onRejected 에 전달됩니다. 예를 들어:

// 외층 then 은 내층 future 와 error 를 얻을 수 있음
new Promise(function(resolve, reject) {
    resolve(new Promise(function(resolve, reject) {
        resolve(12);
        // reject(new Error('reject in nested Promise'));
    }).then(function(future) {
        return future + 1;
    }, function(error) {
        return new Error('reject again in nested Promise');
    }));
}).then(onFulfilled, onRejected);
// => 13 onFulfilled 이 내층 promise 의 최종적인 [[PromiseValue]] 를 받았기 때문
// reject 인 경우, onFulfilled 은 Error('reject again') 를 받음

五.기본 문법

###1.Promise 객체 생성

Promise 를 생성하는 기본 문법은 다음과 같습니다:

// 생성
var promise = new Promise(function(resolve, reject) {
    if (/*...*/) {
        resolve(data);
    }
    else {
        reject(new Error('error occurs'));
    }
});
// 사용
// then
promise.then(function(future) {
    // ...
}, function(error) {
    // ...
});
// catch
promise.catch(function(error) {
    // ...
});

주의:new Promise(func) 시, func즉시 실행됩니다 (generator 가 선언만 하고 실행하지 않는 것과 다름), 실행 과정 중 resolve() 를 만나도 못 본 척 (P.S.간단히 못 본 척으로 이해, 설명은 주석 참조), reject() 를 만나면 즉시 오류를 스로우 (주의:Chrome46 은 즉시 오류 스로우, FF39 는 오류 없음, 其它 polyfill, 예:es6-promisepromise-polyfill 는 오류 없음), 대비 예는 다음과 같습니다:

new Promise(function(resolve, reject) {
    console.log('#1');
    resolve(1);
    console.log('#2');
});
// => #1 #2 오류 없음 (resolve 를 만나도 못 본 척)
// 실제로 내부 속성 [[PromiseStatus]] 과 [[PromiseValue]] 는 변했지만, then 이 제공하는 onFulfilled 콜백이 없어 효과를 볼 수 없음

new Promise(function(resolve, reject) {
    console.log('#1');
    reject(new Error('reject'));
    console.log('#2');
});
// => #1 #2 Chrome46 오류 보고:Uncaught (in promise) Error: reject(…)
// 뒤에 then/catch 가 제공하는 onRejected 콜백이 없어 예외가 소비되지 않음

P.S.reject 의 매개변수는 임의의 타입이어도 되지만, 일반적으로 오류 원인을 나타내는 Error 객체를 전달함

###2.Promise.prototype.then(onFulfilled, onRejected) 그 다음

promiseonFulfilled/onRejected 콜백 handler 를 추가하고, promiseresolve/reject 가 실행될 때 대응하는 handler 를 트리거

주의:promise.then 으로 콜백 함수를 추가할 때, promise 가 이미 fulfilled 또는 rejected 상태인 경우, 해당 메서드는즉시 호출됩니다

Promise 객체를 반환 (체인 호출 가능), 간단히 다음과 같이 이해:

1.onFulfilled 이 트리거된 경우, 해당 객체의 [[PromiseValue]]=onFulfilled 의 반환값, [[PromiseStatus]]=resolved

new Promise(function(resolve, reject) {
    resolve(2);
}).then(function(future) {
    return future * 2;
}, onRejected).then(onFulfilled, onRejected);
// onFulfilled 이 4 를 얻음

2. onRejected 이 트리거된 경우, 해당 객체의 [[PromiseValue]]=onRejected 의 반환값, [[PromiseStatus]]=resolved

특히 주의: 반환 객체의 상태는 resolved 이며, rejected가 아님

new Promise(function(resolve, reject) {
    reject(2);
}).then(null, function(err) {
    return err * 2;
}).then(onFulfilled, onRejected);
// 여전히 onFulfilled 이 4 를 얻음, onRejected 이 아님

P.S.실제로는 [[PromiseValue]]=undefined, [[PromiseStatus]]=pending, 하지만 여전히 위와 같은 상태로 실행

###3.Promise.prototype.catch(onRejected) 캡처

then(null, onRejected) 과 동등하며, Promise 객체를 반환하고, 간단히 다음과 같이 이해:

  1. onReject 이 트리거된 경우, 반환 객체의 [[PromiseValue]]=onReject 의 반환값, [[PromiseStatus]]=resolved

  2. onReject 이始終 트리거되지 않은 경우, catch 를 못 본 척하고, 직접 원래 Promise 객체를 반환

    var p = new Promise(function(resolve, reject) { resolve(2); }).catch(function() { console.log('onRejected'); return false; }); p.then(onFulfilled, onRejected); // onFulfilled 이 2 를 얻음, catch 는 아무 영향도 없음

P.S.실제로는 [[PromiseValue]]=undefined, [[PromiseStatus]]=pending, 하지만 여전히 위와 같은 상태로 실행

###4.Promise.all(iterable)

Promise 객체를 반환하고, iterable 중의 모든 promiseresolve 된 후, onFulfilled 을 트리거하고, 첫 번째 부정 promise 를 만나면 즉시 종료하고, onRejected 을 트리거

//1.전肯定 집합, onFulfilled 을 트리거, 전달되는 매개변수는 집합 중의 모든 promise 의 [[PromiseValue]] 로 구성된 배열    
Promise.all([getPromise(), getPromise()]).then(onFulfilled, onRejected);

//2.전 부정 집합, 첫 번째 부정 promise 를 만나면 종료, onRejected 을 트리거, 전달되는 매개변수는 해당 promise 의 [[PromiseValue]]
// Promise.all([getPromise(true), getPromise(true)]).then(onFulfilled, onRejected);

// 3.일반 집합, 위와 같음
// Promise.all([getPromise(true), getPromise()]).then(onFulfilled, onRejected);

P.S.all, 모두 정상적으로 완료되거나, 오류로 중단되며, 모두 멈춤

###5.Promise.race(iterable) 경주

Promise 객체를 반환하고, iterable 중 임의의 하나의 promiseresolve 되면 즉시 종료하고, onFulfilled 을 트리거하고, 부정 promise 를 만나면 즉시 종료하고, onRejected 을 트리거

// 1.전肯定 집합, 첫 번째肯定 promise 를 만나면 종료, onFulfilled 을 트리거, 전달되는 매개변수는 해당 promise 의 [[PromiseValue]]
// Promise.race([getPromise(), getPromise()]).then(onFulfilled, onRejected);
// 2.전 부정 집합, 첫 번째 부정 promise 를 만나면 종료, onRejected 을 트리거, 전달되는 매개변수는 해당 promise 의 [[PromiseValue]]
// Promise.race([getPromise(true), getPromise(true)]).then(onFulfilled, onRejected);
// 3.일반 집합, 위와 같음
// Promise.race([getPromise(true), getPromise()]).then(onFulfilled, onRejected);

P.S."경주", 결과가肯定인지 부정인지에 관계없이, 가장 빠른 것만

###6.Promise.resolve(value) 해결

1.valuepromise 인 경우, promise 중의 resolve/reject 의 매개변수를 대응하는 onFulfilled/onRejected 에 전달

Promise.resolve(getPromise()).then(onFulfilled, onRejected);
// onFulfilled 이 resolve 의 매개변수를 얻음
Promise.resolve(getPromise(true)).then(onFulfilled, onRejected);
// onRejected 이 reject 의 매개변수를 얻음

2.value 가 thenable 객체 (then 속성을 가진 객체) 인 경우, obj 를 Promise 객체로 래핑하고, 해당 객체의 thenobj.then

Promise.resolve({
    then: function(onFulfilled, onRejected) {
        // onFulfilled(1);
        onRejected(new Error('obj.then err'));

        console.log('obj.then');
    }
}).then(onFulfilled, onRejected);

3.value 가!thenable 값인 경우, 해당 값을 Promise 객체로 래핑하고, 해당 객체의 [[PromiseValue]]=value, [[PromiseStatus]]=resolved, 따라서 then 메서드를 호출할 때, onFulfilled 은 이 값을 받음

Promise.resolve(1).then(onFulfilled, onRejected);
// onFulfilled 이 1 을 얻음

###7.Promise.reject(reason) 거부

reason 을 Promise 객체로 래핑하고, 해당 객체의 [[PromiseValue]]=reason, [[PromiseStatus]]=rejected, 따라서 then 메서드를 호출할 때, onRejected 은 이 값을 받음

주의: 여기에는 thenable/!thenable 의 구분이 없으며, 일률적으로 처리:

// 1.!thenable onRejected 은 Error 객체를 얻음
Promise.reject(new Error('static reject')).then(onFulfilled, onRejected);
// 2.promise onRejected 은 감싸진肯定 Promise 객체를 얻음
// 해당 객체의 [[PromiseValue]]=promise, [[PromiseStatus]]=rejected
Promise.reject(getPromise()).then(onFulfilled, onRejected);
// 3.thenable onRejected 은 {then: xxx} 를 얻음
Promise.reject({
    then: function(onFulfilled, onRejected) {
        console.log('obj.then');
    }
}).then(onFulfilled, onRejected);

완전한 테스트 DEMO:promise 实例

六.고급 사용법

  • 제네레이터 (generator) 와 연동

  • async/await 와 연동

(분량 제한으로, 나중에 설명)

참고 자료

댓글

아직 댓글이 없습니다

댓글 작성