.then () 체인에서 이전 약속 결과에 어떻게 액세스합니까?


650

코드를 약속으로 재구성하고 여러 개의 콜백 으로 구성된 멋진 긴 플랫 약속 체인을 만들었습니다 .then(). 결국 복합 값을 반환하고 여러 중간 약속 결과 에 액세스해야 합니다 . 그러나 시퀀스 중간의 해상도 값은 마지막 콜백 범위에 있지 않습니다. 어떻게 액세스합니까?

function getExample() {
    return promiseA(…).then(function(resultA) {
        // Some processing
        return promiseB(…);
    }).then(function(resultB) {
        // More processing
        return // How do I gain access to resultA here?
    });
}

2
이 질문은 정말 흥미롭고 태그가 붙어 있어도 javascript다른 언어와 관련이 있습니다. 난 그냥 사용 "체인을 깰"대답은 자바와 jdeferred
gontard

답변:


377

사슬을 끊다

체인의 중간 값에 액세스해야하는 경우 필요한 단일 조각으로 체인을 분리해야합니다. 하나의 콜백을 연결하고 그 매개 변수를 여러 번 사용하려고하는 대신 결과 값이 필요한 곳이면 어디에서나 동일한 약속에 여러 개의 콜백을 연결하십시오. 잊지 말고, 약속은 단지 미래의 가치를 나타냅니다 (프록시) ! 선형 체인에서 하나의 약속을 다른 약속에서 파생하는 것 외에도 라이브러리에서 제공하는 약속 결합자를 사용하여 결과 값을 작성하십시오.

이는 매우 간단한 제어 흐름, 명확한 기능 구성 및 쉬운 모듈화로 이어집니다.

function getExample() {
    var a = promiseA(…);
    var b = a.then(function(resultA) {
        // some processing
        return promiseB(…);
    });
    return Promise.all([a, b]).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

Promise.allES6에서만 사용할 수있게 된 후 콜백에서 매개 변수를 제거하는 대신 ES5에서는 then호출이 많은 약속 라이브러리 ( Q , Bluebird , when ,…) 에서 제공하는 멋진 도우미 방법으로 대체됩니다 .spread(function(resultA, resultB) { ….

블루 버드는 또한 그 + 조합을 더 단순하고 (보다 효율적인) 구성 으로 대체 하는 전용 join함수 를 제공합니다 .Promise.allspread


return Promise.join(a, b, function(resultA, resultB) {  });

1
배열 내부의 함수가 순서대로 실행됩니까?
scaryguy

6
@scaryguy : 배열에 함수가 없으며 약속입니다. promiseApromiseB제 (약속을 돌려주는) 기능이 여기에 있습니다.
Bergi

2
@Roland 결코 말하지 않았다 :-)이 답변은 약속이 전혀 없었던 ES5 시대에 작성 되었으며이 spread패턴에 매우 유용했습니다. 보다 현대적인 솔루션은 허용되는 답변을 참조하십시오. 그러나 명시 적 패스 스루 답변을 이미 업데이트 했으므로이 업데이트를 업데이트하지 않는 이유는 없습니다.
Bergi

1
@reify 아니요, 그렇게하지 않아야합니다 . 거부하면 문제가 발생합니다.
Bergi

1
이 예를 이해하지 못합니다. 체인 전체에 값을 전파해야하는 일련의 'then'문이 있으면 이것이 어떻게 문제를 해결하는지 알 수 없습니다. 이전 값이 필요한 약속은 해당 값이 나타날 때까지 해고 될 수 없습니다. 또한 Promise.all ()은 단순히 목록의 모든 약속이 끝날 때까지 기다립니다. 주문을 강요하지 않습니다. 따라서 모든 이전 값에 액세스 할 수있는 각 '다음'기능이 필요하며 예제가 어떻게 작동하는지 알 수 없습니다. 나는 그것을 믿거 나 이해하지 못하기 때문에 당신의 모범을 따라 우리를 안내해야합니다.
David Spector

238

ECMAScript 하모니

물론이 문제는 언어 디자이너들에게도 인정되었습니다. 그들은 많은 일을했고 비동기 기능 제안은 마침내 그것을 만들었습니다.

ECMAScript 8

then비동기 함수 (호출 될 때 약속을 반환)에서와 같이 약속이 직접 해결 될 때까지 기다릴 수 있으므로 단일 호출 또는 콜백 함수가 더 이상 필요하지 않습니다 . 또한 조건, 루프 및 try-catch-clauses와 같은 임의의 제어 구조를 제공하지만 편의상 여기서는 필요하지 않습니다.

async function getExample() {
    var resultA = await promiseA(…);
    // some processing
    var resultB = await promiseB(…);
    // more processing
    return // something using both resultA and resultB
}

ECMAScript 6

ES8을 기다리는 동안 이미 비슷한 종류의 구문을 사용했습니다. ES6에는 생성기 기능 이있어 임의로 배치 된 yield키워드 에서 실행을 여러 조각으로 나눌 수 있습니다. 이러한 슬라이스는 서로 독립적으로, 심지어 비동기식으로도 실행될 수 있으며, 다음 단계를 실행하기 전에 약속 해결을 기다릴 때 수행하는 작업입니다.

이 (같은 전용 도서관이다 공동 또는 task.js는 )뿐만 아니라 많은 약속 라이브러리 도우미 기능이 ( Q , 블루 버드 , , ...) 할 것을 이 비동기 단계별 실행 당신이 그 (것)들에게 발전기 기능이를 줄 때 당신을 약속을 산출합니다.

var getExample = Promise.coroutine(function* () {
//               ^^^^^^^^^^^^^^^^^ Bluebird syntax
    var resultA = yield promiseA(…);
    // some processing
    var resultB = yield promiseB(…);
    // more processing
    return // something using both resultA and resultB
});

이것은 버전 4.0 이후 Node.js에서 작동했으며, 일부 브라우저 (또는 개발자 에디션)는 비교적 초기에 생성기 구문을 지원했습니다.

ECMAScript 5

그러나 역 호환성을 원하거나 필요로하는 경우에는 트랜스 파일러가없는 것을 사용할 수 없습니다. 생성기 기능과 비동기 기능은 모두 현재 툴링에서 지원됩니다 (예 : 생성기비동기 기능 에 대한 Babel 문서 참조) .

그리고 비동기 프로그래밍을 용이하게 하기위한 다른 많은 컴파일 -JS 언어 도 있습니다. 이들은 보통 await(예 : Iced CoffeeScript ) 와 유사한 구문을 사용 하지만 Haskell과 같은 do표기법 (예 : LatteJs , monadic , PureScript 또는 LispyScript ) 을 특징으로하는 다른 구문 도 있습니다 .


@ Bergi 외부 코드에서 비동기 함수 examle getExample ()을 기다려야합니까?
arisalexis

@arisalexis : 예, getExample여전히 약속을 반환하는 함수이며 다른 답변의 함수와 동일하게 작동하지만 구문이 더 좋습니다. 당신은 할 수 await다른에서 전화 async기능을하거나, 체인 수 .then()의 결과로.
Bergi

1
궁금합니다. 질문 한 후에 왜 직접 질문에 대답하셨습니까? 여기에 좋은 토론이 있지만 궁금합니다. 물어 본 후에 스스로 답을 찾았 을까요?
granmoe

@granmoe : 나는 정식 복제 대상으로 목적에 대한 전체 토론을 게시했습니다
Bergi

ECMAScript 6 예제에서 Promise.coroutine (예 : Bluebird 또는 다른 라이브러리를 사용하지 않고 일반 JS 만 사용)을 사용하지 않는 (매우 힘든) 방법이 있습니까? 나는 비슷한 것을 생각 steps.next().value.then(steps.next)...했지만 작동하지 않았다.
학습자는 이름이 없습니다.

102

동기 검사

나중에 필요한 값을 변수에 지정하고 동기 검사를 통해 값을 가져옵니다. 이 예제는 블루 버드 .value()방법을 사용하지만 많은 라이브러리가 유사한 방법을 제공합니다.

function getExample() {
    var a = promiseA(…);

    return a.then(function() {
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // a is guaranteed to be fulfilled here so we can just retrieve its
        // value synchronously
        var aValue = a.value();
    });
}

원하는만큼 많은 값을 사용할 수 있습니다.

function getExample() {
    var a = promiseA(…);

    var b = a.then(function() {
        return promiseB(…)
    });

    var c = b.then(function() {
        return promiseC(…);
    });

    var d = c.then(function() {
        return promiseD(…);
    });

    return d.then(function() {
        return a.value() + b.value() + c.value() + d.value();
    });
}

6
이것은 내가 가장 좋아하는 답변입니다 : 라이브러리 또는 언어 기능에 대한 읽기 쉽고, 확장 가능하며, 최소한의 의존
Jason

13
@Jason : 어, " 도서관 기능에 대한 최소한의 의존 "? 동기 검사는 라이브러리 기능이며 부팅 할 표준이 아닙니다.
Bergi

2
나는 그가 도서관 고유의 기능을 의미한다고 생각한다
deathgaze

54

중첩 및 폐쇄

변수 범위를 유지하기 위해 클로저를 사용하는 경우 (이 경우 성공 콜백 함수 매개 변수)는 자연스러운 JavaScript 솔루션입니다. 약속을 통해 콜백을 임의로 중첩하고 평탄화 할 수 있습니다. .then()콜백은 내부 콜 범위를 제외하고 의미 적으로 동일합니다.

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(function(resultB) {
            // more processing
            return // something using both resultA and resultB;
        });
    });
}

물론 이것은 들여 쓰기 피라미드를 만들고 있습니다. 들여 쓰기가 너무 커지면 여전히 오래된 도구를 사용하여 운명피라미드에 대응할 수 있습니다.
이론적으로는 항상 모든 수준의 클로저를 명시 적으로 만들어 두 개 이상의 네 스팅 수준을 피할 수 있습니다.

function getExample() {
    // preprocessing
    return promiseA(…).then(makeAhandler(…));
}
function makeAhandler(…)
    return function(resultA) {
        // some processing
        return promiseB(…).then(makeBhandler(resultA, …));
    };
}
function makeBhandler(resultA, …) {
    return function(resultB) {
        // more processing
        return // anything that uses the variables in scope
    };
}

또한 이런 종류의 도우미 기능을 사용할 수 있습니다 부분 응용 프로그램 과 같은 _.partial에서 밑줄 / lodash 또는 기본 .bind()방법은 더 감소 들여 쓰기 :

function getExample() {
    // preprocessing
    return promiseA(…).then(handlerA);
}
function handlerA(resultA) {
    // some processing
    return promiseB(…).then(handlerB.bind(null, resultA));
}
function handlerB(resultA, resultB) {
    // more processing
    return // anything that uses resultA and resultB
}

5
Nolan Lawson의 논문 pouchouch.com/2015/05/18/we-have-a-problem-with-promises.html 에 대한 '고급 실수 # 4'에 대한 해결책으로 동일한 제안이 제공됩니다 . 잘 읽었습니다.
Robert

2
이것은 bindMonads 의 기능입니다. Haskell은 구문 설탕 (do-notation)을 제공하여 비동기 / 대기 구문처럼 보이게합니다.
zeronone

50

명시 적 통과

콜백 중첩과 마찬가지로이 기술은 클로저에 의존합니다. 그러나 체인은 평평한 상태를 유지합니다. 최신 결과 만 전달하는 대신 모든 단계마다 일부 상태 객체가 전달됩니다. 이 상태 개체는 이전 작업의 결과를 누적하여 나중에 다시 필요한 모든 값과 현재 작업의 결과를 모두 전달합니다.

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(b => [resultA, b]); // function(b) { return [resultA, b] }
    }).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

여기서 작은 화살표 b => [resultA, b]는를 닫고 resultA두 결과의 배열을 다음 단계로 전달하는 함수입니다 . 매개 변수 파괴 구문을 사용하여 단일 변수로 다시 분류합니다.

ES6로 구조화가 가능해지기 전에, .spread()많은 promise 라이브러리 ( Q , Bluebird , when ,…)에 의해 호출되는 멋진 도우미 메소드 가 제공되었습니다 . 각 배열 요소마다 하나씩 여러 매개 변수가있는 함수를 사용합니다 .spread(function(resultA, resultB) { ….

물론 여기에 필요한 클로저는 일부 도우미 기능, 예를 들어

function addTo(x) {
    // imagine complex `arguments` fiddling or anything that helps usability
    // but you get the idea with this simple one:
    return res => [x, res];
}


return promiseB(…).then(addTo(resultA));

또는 Promise.all어레이에 대한 약속을 생성하기 위해 고용 할 수 있습니다 .

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return Promise.all([resultA, promiseB(…)]); // resultA will implicitly be wrapped
                                                    // as if passed to Promise.resolve()
    }).then(function([resultA, resultB]) {
        // more processing
        return // something using both resultA and resultB
    });
}

그리고 배열뿐만 아니라 임의로 복잡한 객체를 사용할 수도 있습니다. 예를 들어, 다른 도우미 기능을 사용 _.extend하거나 사용 Object.assign하는 경우 :

function augment(obj, name) {
    return function (res) { var r = Object.assign({}, obj); r[name] = res; return r; };
}

function getExample() {
    return promiseA(…).then(function(resultA) {
        // some processing
        return promiseB(…).then(augment({resultA}, "resultB"));
    }).then(function(obj) {
        // more processing
        return // something using both obj.resultA and obj.resultB
    });
}

이 패턴은 플랫 체인을 보장하고 명시 적 상태 객체는 선명도를 향상시킬 수 있지만 긴 체인에는 지루할 것입니다. 특히 산발적으로 만 필요한 상태 인 경우에도 모든 단계를 거쳐야합니다. 이 고정 인터페이스를 사용하면 체인의 단일 콜백이 다소 밀접하게 결합되어 변경하기가 어렵습니다. 단일 단계를 더 어렵게 만들 수 있으며 다른 모듈에서 직접 콜백을 제공 할 수 없습니다. 상태를 신경 쓰는 상용구 코드로 항상 래핑해야합니다. 위와 같은 추상 도우미 기능은 고통을 약간 완화시킬 수 있지만 항상 존재합니다.


첫째, 나는 구문을 Promise.all권장 하지 않는다고 생각하지 않습니다 (구조화가 그것을 대체하고 사람들로 전환 하면 사람들에게 종종 예기치 않은 결과 .spread를 줄 때 ES6에서는 작동하지 않습니다 then. 기능 보강-왜 당신이 필요한지 잘 모르겠습니다. 사용 증가시키기 위해 -. 약속 프로토 타입에 일을 추가하는 것은 (현재 지원되지 않는)이 서브 클래스로 확장 할 가정된다 어쨌든 ES6의 약속을 확장하는 허용 방법이 아니다
벤자민 Gruenbaum

@ BenjaminGruenbaum : " 구문 생략Promise.all " 은 무슨 뜻 입니까? 이 답변의 방법 중 어느 것도 ES6에서 중단되지 않습니다. a spread를 파괴로 전환 then해도 문제가 없어야합니다. Re.prototype.augment : 누군가 알아 차릴 수 있다는 것을 알았고, 가능성을 탐색하고 편집하려고했습니다.
Bergi

배열 구문으로 말 return [x,y]; }).spread(...대신 return Promise.all([x, y]); }).spread(...하는 ES6 destructuring 설탕에 대한 확산을 교환하고 또한 약속 다른 모든 것들과 다르게 배열을 반환 취급하여 이상한 가장자리 경우가 아닐 것이다 변경되지 것입니다.
Benjamin Gruenbaum

3
아마도 가장 좋은 대답 일 것입니다. 약속은 "기능적 반응성 프로그래밍"-빛이며, 이것이 종종 사용되는 솔루션입니다. 예를 들어 BaconJs에는 #combineTemplate이있어 결과를 체인으로 전달되는 객체로 결합 할 수 있습니다
U Avalos

1
@CapiEtheriel 답변은 ES6가 오늘날처럼 널리 보급되지 않았을 때 작성되었습니다. 예, 아마도 예제를 바꿔야 할시기입니다.
Bergi

35

가변 상황

사소한 (그러나 우아하지 않고 오류가 발생하기 쉬운) 해결책은 더 높은 범위의 변수 (체인의 모든 콜백이 액세스 할 수있는)를 사용하고 결과 값을 얻을 때 변수에 쓰는 것입니다.

function getExample() {
    var resultA;
    return promiseA(…).then(function(_resultA) {
        resultA = _resultA;
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // more processing
        return // something using both resultA and resultB
    });
}

많은 변수 대신 결과가 동적으로 생성 된 속성으로 저장되는 (처음에는 비어있는) 객체를 사용할 수도 있습니다.

이 솔루션에는 몇 가지 단점이 있습니다.

  • 가변 상태는 추악 하고 전역 변수는 악하다 .
  • 이 패턴은 함수 경계에서 작동하지 않으므로 선언이 공유 범위를 벗어나지 않아야하므로 함수를 모듈화하는 것이 더 어렵습니다.
  • 변수의 범위는 변수가 초기화되기 전에 액세스하는 것을 막지 않습니다. 경쟁 조건이 발생할 수있는 복잡한 약속 구성 (루프, 분기, 예외)에 특히 적합합니다. 권장 하는 선언적 디자인 을 통해 명시 적으로 상태를 전달 하면이를 방지 할 수있는보다 명확한 코딩 스타일이 적용됩니다.
  • 공유 변수의 범위를 올바르게 선택해야합니다. 예를 들어 상태가 인스턴스에 저장된 경우와 같이 여러 병렬 호출 간의 경쟁 조건을 방지하려면 실행 된 함수에 로컬이어야합니다.

블루 버드 라이브러리를 사용하여 함께 전달되는 객체의 사용을 장려하고 자신의 bind()방법을 약속 체인 문맥 객체를 할당 할 수 있습니다. 사용할 수없는 this키워드 를 통해 각 콜백 함수에서 액세스 할 수 있습니다 . 객체 속성은 변수보다 감지되지 않은 오타가 발생하기 쉽지만 패턴은 매우 영리합니다.

function getExample() {
    return promiseA(…)
    .bind({}) // Bluebird only!
    .then(function(resultA) {
        this.resultA = resultA;
        // some processing
        return promiseB(…);
    }).then(function(resultB) {
        // more processing
        return // something using both this.resultA and resultB
    }).bind(); // don't forget to unbind the object if you don't want the
               // caller to access it
}

이 접근법은 .bind를 지원하지 않는 promise 라이브러리에서 쉽게 시뮬레이션 할 수 있습니다 (약간 더 장황한 방식으로 표현에 사용할 수는 없지만).

function getExample() {
    var ctx = {};
    return promiseA(…)
    .then(function(resultA) {
        this.resultA = resultA;
        // some processing
        return promiseB(…);
    }.bind(ctx)).then(function(resultB) {
        // more processing
        return // something using both this.resultA and resultB
    }.bind(ctx));
}

.bind()메모리 누수 방지를 위해 불필요
Esailija

@Esailija : 그러나 반환 된 약속이 컨텍스트 객체에 대한 참조를 보유하지 않습니까? 물론 가비지 수집은 나중에 처리합니다. 약속이 폐기되지 않는 한 "누설"이 아닙니다.
Bergi

예 그러나 약속은 자신의 성취 값과 오류 원인에 대한 참조를 보관 유지 ...하지만 그렇지 않은, 그래서 아무것도없이 약속에 대한 참조를 보유하지
Esailija

4
제가이 서두에 거의 투표했을 때이 답을 두 가지로 나누십시오! "사소한 (그러나 우아하지 않고 오류가 발생하기 쉬운) 해결책"은 가장 깨끗하고 간단한 해결책이라고 생각합니다. 왜냐하면 대답은 받아 들여진 자기 대답보다 더 이상 클로저와 변경 가능한 상태에 의존하지 않기 때문입니다. 폐쇄는 전 세계적으로나 사악하지도 않습니다. 이 접근법에 반대되는 주장은 전제를 감안할 때 나에게 의미가 없습니다. "놀랍고 평평한 약속 체인"에 어떤 모듈화 문제가있을 수 있습니까?
jib

2
위에서 말했듯이 약속은 "기능적 반응성 프로그래밍"입니다. 이것은 FRP의 반 패턴입니다
U Avalos

15

"변경 가능한 상황 별 상태"에 대한 덜 가혹한 스핀

약속 범위에서 중간 결과를 수집하기 위해 로컬 범위의 객체를 사용하는 것은 제기 한 질문에 대한 합리적인 접근 방식입니다. 다음 스 니펫을 고려하십시오.

function getExample(){
    //locally scoped
    const results = {};
    return promiseA(paramsA).then(function(resultA){
        results.a = resultA;
        return promiseB(paramsB);
    }).then(function(resultB){
        results.b = resultB;
        return promiseC(paramsC);
    }).then(function(resultC){
        //Resolve with composite of all promises
        return Promise.resolve(results.a + results.b + resultC);
    }).catch(function(error){
        return Promise.reject(error);
    });
}
  • 전역 변수가 잘못되었으므로이 솔루션은 로컬 범위 변수를 사용하여 피해를주지 않습니다. 함수 내에서만 액세스 할 수 있습니다.
  • 변하기 쉬운 상태는 추악하지만, 추악한 방법으로 상태를 바꾸지는 않습니다. 추악한 변경 가능한 상태는 전통적으로 함수 인수 또는 전역 변수의 상태를 수정하는 것을 의미하지만,이 방법은 약속 결과를 집계하기위한 목적으로 만 존재하는 로컬 범위 변수의 상태를 변경합니다. 일단 약속이 해결되면.
  • 중간 약속은 결과 개체의 상태에 액세스하는 것이 방지되지 않지만 체인의 약속 중 하나가 악의적으로 진행되어 결과를 방해하는 무서운 시나리오는 아닙니다. 약속의 각 단계에서 값을 설정하는 책임은이 기능에 국한되어 있으며 전체 결과는 정확하거나 잘못 될 것입니다 ... 생산을 위해 몇 년 후에 자랄 버그는 아닙니다 !)
  • getExample 함수를 호출 할 때마다 결과 변수의 새 인스턴스가 작성되므로 병렬 호출에서 발생할 수있는 경쟁 조건 시나리오를 소개하지 않습니다.

1
최소한 Promiseantipattern 생성자를 피하십시오 !
Bergi

@ Bergi 덕분에, 당신이 그것을 언급 할 때까지 그것이 반 패턴이라는 것을 알지 못했습니다!
Jay

ES5를 사용하고 있었고 약속과 함께 사용할 다른 라이브러리를 추가하고 싶지 않았습니다.
nilakantha singh deo

8

노드 7.4는 이제 조화 플래그를 사용하여 비동기 / 대기 호출을 지원합니다.

이 시도:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

다음을 사용하여 파일을 실행하십시오.

node --harmony-async-await getExample.js

가능한 간단!


8

요즘에도 당신과 같은 몇 가지 질문이 있습니다. 마지막으로 질문에 대한 좋은 해결책을 찾았습니다. 간단하고 읽기 쉽습니다. 이것이 당신을 도울 수 있기를 바랍니다.

how-to-chain-javascript-promises 에 따르면

좋아, 코드를 보자.

const firstPromise = () => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('first promise is completed');
            resolve({data: '123'});
        }, 2000);
    });
};

const secondPromise = (someStuff) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('second promise is completed');
            resolve({newData: `${someStuff.data} some more data`});
        }, 2000);
    });
};

const thirdPromise = (someStuff) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log('third promise is completed');
            resolve({result: someStuff});
        }, 2000);
    });
};

firstPromise()
    .then(secondPromise)
    .then(thirdPromise)
    .then(data => {
        console.log(data);
    });

4
이것은 실제로 체인의 이전 결과에 액세스하는 방법에 대한 질문에 대답하지 않습니다.
Bergi

2
모든 약속은 이전의 가치를 얻을 수 있습니다. 당신의 의미는 무엇입니까?
yzfdjzwl

1
질문의 코드를 살펴보십시오. 목표는 약속 된 약속의 결과를 얻는 것이 아니라 그 이전의 결과를 얻는 것 .then입니다. 예를 들어 thirdPromise의 결과에 액세스합니다 firstPromise.
Bergi

6

babel-node<6 버전을 사용하는 또 다른 대답

사용 async - await

npm install -g babel@5.6.14

example.js:

async function getExample(){

  let response = await returnPromise();

  let response2 = await returnPromise2();

  console.log(response, response2)

}

getExample()

그런 다음 달리기 babel-node example.js와 짜잔!


1
예, 게시 한 직후에했습니다. 그래도 언젠가 ES7을 사용할 수 있다고 말하는 대신 ES7을 사용하여 실제로 시작하고 실행하는 방법을 설명하기 때문에 그대로 두겠습니다.
Anthony

1
아 맞다. 나는 이것들을위한 "실험적인"플러그인 들이 이미 여기 있다고 말하기 위해 대답을 업데이트해야한다 .
Bergi

2

전역 변수를 사용하는 데 큰 관심이 없기 때문에 내 코드 에서이 패턴을 사용하지 않을 것입니다. 그러나 핀치에서 작동합니다.

사용자는 약속 된 몽구스 모델입니다.

var globalVar = '';

User.findAsync({}).then(function(users){
  globalVar = users;
}).then(function(){
  console.log(globalVar);
});

2
이 패턴은 이미에 자세히 설명되어 공지 사항 변경 가능한 상황에 맞는 상태 대답 (- 나도 큰 팬이 아니에요 그것은 추한 이유도)
BERGI

귀하의 경우, 패턴은 쓸모없는 것 같습니다. 당신은 전혀 필요하지 않습니다 globalVar, 그냥 User.findAsync({}).then(function(users){ console.log(users); mongoose.connection.close() });?
Bergi

1
내 코드에는 개인적으로 필요하지 않지만 사용자는 두 번째 기능에서 더 많은 비동기를 실행 한 다음 원래 Promise 호출과 상호 작용해야 할 수 있습니다. 그러나 언급 했듯이이 경우 발전기를 사용합니다. :)
Anthony

2

순차적 실행기 nsynjs를 사용하는 또 다른 대답 :

function getExample(){

  var response1 = returnPromise1().data;

  // promise1 is resolved at this point, '.data' has the result from resolve(result)

  var response2 = returnPromise2().data;

  // promise2 is resolved at this point, '.data' has the result from resolve(result)

  console.log(response, response2);

}

nynjs.run(getExample,{},function(){
    console.log('all done');
})

업데이트 : 추가 작업 예제

function synchronousCode() {
     var urls=[
         "https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js",
         "https://ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js",
         "https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"
     ];
     for(var i=0; i<urls.length; i++) {
         var len=window.fetch(urls[i]).data.text().data.length;
         //             ^                   ^
         //             |                   +- 2-nd promise result
         //             |                      assigned to 'data'
         //             |
         //             +-- 1-st promise result assigned to 'data'
         //
         console.log('URL #'+i+' : '+urls[i]+", length: "+len);
     }
}

nsynjs.run(synchronousCode,{},function(){
    console.log('all done');
})
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>


1

블루 버드를 사용할 때 .bind메소드를 사용 하여 약속 체인에서 변수를 공유 할 수 있습니다 .

somethingAsync().bind({})
.spread(function (aValue, bValue) {
    this.aValue = aValue;
    this.bValue = bValue;
    return somethingElseAsync(aValue, bValue);
})
.then(function (cValue) {
    return this.aValue + this.bValue + cValue;
});

자세한 내용은이 링크를 확인하십시오.

http://bluebirdjs.com/docs/api/promise.bind.html


이 패턴은
Mutable

1
function getExample() {
    var retA, retB;
    return promiseA(…).then(function(resultA) {
        retA = resultA;
        // Some processing
        return promiseB(…);
    }).then(function(resultB) {
        // More processing
        //retA is value of promiseA
        return // How do I gain access to resultA here?
    });
}

쉬운 방법 : D


이 답변을 눈치 채 셨나요?
Bergi

1

RSVP의 해시를 사용할 수 있다고 생각합니다.

아래와 같은 것 :

    const mainPromise = () => {
        const promise1 = new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('first promise is completed');
                resolve({data: '123'});
            }, 2000);
        });

        const promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                console.log('second promise is completed');
                resolve({data: '456'});
            }, 2000);
        });

        return new RSVP.hash({
              prom1: promise1,
              prom2: promise2
          });

    };


   mainPromise()
    .then(data => {
        console.log(data.prom1);
        console.log(data.prom2);
    });

네, 같은입니다 솔루션 만 개체 대신의 배열. Promise.all
Bergi

0

해결책:

'bind'를 사용하여 나중에 'then'함수에 범위의 중간 값을 명시 적으로 넣을 수 있습니다. 약속의 작동 방식을 변경할 필요가 없으며 오류가 이미 전파되는 것처럼 값을 전파하기 위해 한두 줄의 코드 만 필요합니다.

다음은 완전한 예입니다.

// Get info asynchronously from a server
function pGetServerInfo()
    {
    // then value: "server info"
    } // pGetServerInfo

// Write into a file asynchronously
function pWriteFile(path,string)
    {
    // no then value
    } // pWriteFile

// The heart of the solution: Write formatted info into a log file asynchronously,
// using the pGetServerInfo and pWriteFile operations
function pLogInfo(localInfo)
    {
    var scope={localInfo:localInfo}; // Create an explicit scope object
    var thenFunc=p2.bind(scope); // Create a temporary function with this scope
    return (pGetServerInfo().then(thenFunc)); // Do the next 'then' in the chain
    } // pLogInfo

// Scope of this 'then' function is {localInfo:localInfo}
function p2(serverInfo)
    {
    // Do the final 'then' in the chain: Writes "local info, server info"
    return pWriteFile('log',this.localInfo+','+serverInfo);
    } // p2

이 솔루션은 다음과 같이 호출 할 수 있습니다.

pLogInfo("local info").then().catch(err);

(참고 :이 솔루션의보다 복잡하고 완전한 버전은 테스트되었지만이 예제 버전은 아니므로 버그가있을 수 있습니다.)


이것은 중첩 (및) 클로저 답변 동일한 패턴 인 것 같습니다
Bergi

비슷해 보입니다. 새로운 Async / Await 구문에는 인수의 자동 바인딩이 포함되어 있으므로 모든 비동기 함수에 모든 인수를 사용할 수 있다는 것을 알게되었습니다. 약속을 버리고 있습니다.
David Spector

async/ await여전히 약속을 사용하는 것을 의미합니다. 포기할 수있는 것은 then콜백 통화입니다.
Bergi

-1

약속에 대해 배운 것은 가능한 경우 반환 값을 참조하지 않기 때문에 약속을 사용하는 입니다. async / await 구문이 특히 실용적입니다. 오늘날 모든 최신 브라우저와 노드는 그것을 지원합니다 : https://caniuse.com/#feat=async-functions 는 간단한 동작이며 코드는 동기 코드를 읽는 것과 같습니다. 콜백을 잊어 버립니다 ...

약속을 참조 해야하는 경우 독립 및 관련되지 않은 장소에서 생성 및 해결이 발생할 때입니다. 따라서 인위적 연관성과 아마도 "먼"약속을 해결하기위한 이벤트 리스너는 약속을 지연으로 표시하는 것을 선호합니다. 다음 코드는 유효한 es5에서이를 구현합니다.

/**
 * Promise like object that allows to resolve it promise from outside code. Example:
 *
```
class Api {
  fooReady = new Deferred<Data>()
  private knower() {
    inOtherMoment(data=>{
      this.fooReady.resolve(data)
    })
  }
}
```
 */
var Deferred = /** @class */ (function () {
  function Deferred(callback) {
    var instance = this;
    this.resolve = null;
    this.reject = null;
    this.status = 'pending';
    this.promise = new Promise(function (resolve, reject) {
      instance.resolve = function () { this.status = 'resolved'; resolve.apply(this, arguments); };
      instance.reject = function () { this.status = 'rejected'; reject.apply(this, arguments); };
    });
    if (typeof callback === 'function') {
      callback.call(this, this.resolve, this.reject);
    }
  }
  Deferred.prototype.then = function (resolve) {
    return this.promise.then(resolve);
  };
  Deferred.prototype.catch = function (r) {
    return this.promise.catch(r);
  };
  return Deferred;
}());

번역 된 필자의 타이프 스크립트 프로젝트 :

https://github.com/cancerberoSgx/misc-utils-of-mine/blob/2927c2477839f7b36247d054e7e50abe8a41358b/misc-utils-of-mine-generic/src/promise.ts#L31

더 복잡한 경우에는 종종 테스트하고 입력 한 종속성이없는이 작은 약속 유틸리티를 사용합니다. p-map은 여러 번 유용했습니다. 그는 대부분의 사용 사례를 다루었다고 생각합니다.

https://github.com/sindresorhus?utf8=%E2%9C%93&tab=repositories&q=promise&type=source&language=


변경 가능한 상황 또는 동기 검사를 제안하는 것처럼 들립 니까?
Bergi

@bergi 처음으로 그 이름을 사용합니다. 목록에 감사를 추가합니다 .Deferred-BTW라는 이름으로 이런 종류의 자기 인식 약속을 알고 있습니다. 약속 작성 및 해결의 책임이 독립적 인 경우에는 약속을 해결하기 위해 이들을 연관시킬 필요가없는 경우에 종종이 패턴이 필요합니다. 나는 당신의 모범과 수업에 적응하지는 않았지만 동등한 것을 사용했습니다.
Cancerbero September
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.