.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 : 배열에 함수가 없으며 약속입니다. promiseA및 promiseB제 (약속을 돌려주는) 기능이 여기에 있습니다.
— 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.