.then () 체인에서 이전 약속 결과에 어떻게 액세스 할 수 있습니까?
내 코드를 약속로 재구성하고 여러 콜백 으로 구성된 멋진 긴 평면 약속을 체인 구축했습니다 .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?
});
}
끈끈이
체인의 중간 값에 액세스해야하는 경우 필요한 단일 조각으로 체인을 분리합니다. 하나의 복수를 첨부하고 어떻게 하나의 여러 가지 변수를 여러 번 사용하는 대신에 여러 번 사용하는 것입니다. 잊지 마세요. 약속은 미래 가치를 근거뿐입니다 . 선형 체인의 다른 약속에서 하나의 약속을 도출 한 다음 라이브러리에서 제공하는 약속 결합 튼 사용하여 결과 값을 만듭니다.
이를 통해 매우 간단한 제어 흐름, 명확한 기능 구성 및 간편한 모듈화가 가능합니다.
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.all
ES6 많은 경우에만 사용할 수있게 된 이후부터 대체의 매개 변수 구조화 대신, ES5에서는 then
호출이 promise 라이브러리 ( Q , Bluebird , when , when ,,) 에서 제공하는 멋진 도우미 메서드로 대체 .spread(function(resultA, resultB) { …
됩니다.
Bluebird는 또한 해당 + 조합을 더 간단하고 용이 한 구조 로 대체 하는 전용 join
기능 을 제공합니다 .Promise.all
spread
…
return Promise.join(a, b, function(resultA, resultB) { … });
ECMAScript 하모니
물론이 문제는 언어 디자이너들에게도 인식되었습니다. 그들이 많은 일을 제안 함수 제안은 마침내 만들었습니다.
ECMAScript 8
then
더 이상 단일 호출 또는 호출 함수 가 필요하지 않습니다 . 약속 함수 (호출 될 때 promise를 반환 함)에서 약속이 직접 해결되기를 기다릴 수 있습니다. 또한 조건, 루프 및 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
있습니다. 각각의 단계는 서로 독립적으로, 심지어는 단계 식으로 실행될 수 있고 다음 기다리고 싶을 실행하기 전에 약속입니다.
전용 라이브러리 (예 : co 또는 task.js )가는 많은 promise 라이브러리에는 다음과 같은 다음과 같은 함수를 제공 할 때 필요한 함수를 생성 하는 단계별 실행 을 수행 하는 도우미 함수 ( Q , Bluebird , when ,…)가 있습니다. 약속을 많이합니다.
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
, (예 : 아이스 커피 스크립트 ) 와 유사한 구문을 사용 하지만 하스켈 유사한과 do
표기법 (예 : LatteJs , 모나드 , PureScript 또는 LispyScript ) 을 특징으로하는 다른 구문 도 계명 있습니다 .
동기 검사
나중에 필요한 값에 대한 약속을 변수에 할당 한 다음 동기 검사를 통해 값을 가져옵니다. 이 예제는 bluebird의 .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();
});
}
중첩 (및) 클로저
변수의 범위 (이 경우 성공하는 함수 호출 변수)를 유지하기 위해 클로저를 사용하는 것이 자연스러운 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;
});
});
}
물론 여기에서 쓰기 작성을합니다. 들여 쓰기가 너무 커져도 둠 의 피라미드에 대응하기 위해 이전 도구를 적용 할 수 있습니다 . 모듈화하고, 추가로 명명 된 함수를 사용하고, 변수가 더 이상 필요하지 않을 약속 체인을 평평하게 만듭니다.
이론적으로는 (모든 클로저를 명시 적으로 만들어) 항상 2 개 이상의 중첩 수준을 피할 수 있고, 합당만큼 많이 사용합니다.
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
}
명시 적 통과
중첩 중첩과 유사 하게이 기술은 클로저에 의존합니다. 그러나 체인은 평평하게 유지됩니다. 최신 결과 만 전달하는 대신에 전달됩니다. 현재 상태는 이전 작업의 결과를 지금부터 다시 필요한 모든 값과 현재 작업의 결과를 전달합니다.
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에서 destructuring을 사용할 수있게되기 전에 많은 .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
});
}
이 패턴은 플랫 체인을 보장하고 명시 적 상태는 명확하게 표현할 수 있습니다. 특히 상태가 산발적으로 만 필요할 때 모든 단계를 통과해야합니다. 이 고정 인터페이스를 사용하면 체인의 단일성이 다소하게 결합되고 변경에 유연하지. 단일 단계를 분해하는 것이 더 어렵고 많은 것은 다른 모듈에서 직접 제공 할 수 없습니다. 항상 상태를 고려하는 상용구 코드로 래핑해야합니다. 위와 같은 추상 도우미 기능은 고통을 조금 덜어 줄 수 있도록 항상 존재합니다.
가변형 상태
사소한 (하지만 우아하지 않고 오류가 발생하기 쉬운) 해결은 더 높은 범위의 변수를 사용하고 결과 값을 얻을 때 그 변수에 쓰는 것입니다.
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
});
}
많은 변수 대신 결과가 동적으로 생성 된 속성으로 저장되는 (처음에는 비어있는) 개체를 사용할 수 있습니다.
이 솔루션에는 몇 가지 문제가 있습니다.
- 변경 가능한 상태 못생긴 및 글로벌 변수는 악 이다 .
- 이 패턴은 함수 경계를 넘어서 작동하지 않습니다. 함수를 모듈화하는 것은 해결하기 때문에 더 어렵습니다.
- 변수의 범위는 초기화되기 전에 변수에 액세스하는 것을 방지하지 않습니다. 이는 특히 경쟁 조건이 보관 수있는 복잡한 약속 구성 (루프, 분기, 발췌)에서 보관 수 있습니다. 명시 적으로 상태를 전달하면 격려를 약속 하는 선언적 디자인 은이를 방지 할 수있는 더 많은 코딩 스타일을 강요합니다.
- 변수의 범위를 선택 공유해야합니다. 예를 들어 인스턴스에 상태가 저장된 경우처럼 여러 호출 호출 경합 상태를 방지 예측 실행 된 함수에 로컬해야합니다.
버드 라이브러리를 블루 사용하여 함께 전달되는 object-의 사용을 장려하고 자신의 bind()
방법을 약속 체인 문맥 object-를 할당 할 수 있습니다. 확장 된 사용할 수없는 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));
}
"Mutable contextual state"에 대한 덜 콘서트
프로 미스 체인에서 중간을 수집하기 위해 로컬 범위 개체를 사용하는 것은 제기 한 질문에 대한 합리적인 접근 방식입니다. 다음 스 니펫을 고려하십시오.
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 함수를 호출 할 때마다 결과 변수의 새 인스턴스가 생성되기 때문에 발생하는 경쟁 조건 시나리오는 도입되지 않습니다.
이제 Node 7.4는 조화 플래그를 사용하여 async / await 호출을 지원합니다.
이 시도 :
async function getExample(){
let response = await returnPromise();
let response2 = await returnPromise2();
console.log(response, response2)
}
getExample()
다음을 사용하여 파일을 실행하십시오.
node --harmony-async-await getExample.js
최대한 간단합니다!
요즘 나도 당신 같은 질문을 몇 가지 만나고 싶어요. 마침내 질문에 대한 좋은 해결책을 찾았습니다. 간단하고 읽을 수 있습니다. 도움이 되셨기를 바랍니다.
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);
});
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
하고 짜잔!
나는 변수 전역을 사용하는 것을 좋아하지 않기 때문에 패턴을 내 코드에서 사용하지 않을 것입니다. 그러나 꼬집음으로 작동합니다.
사용자는 약속 된 몽구스 모델입니다.
var globalVar = '';
User.findAsync({}).then(function(users){
globalVar = users;
}).then(function(){
console.log(globalVar);
});
순차 실행기 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>
bluebird를 사용할 때 .bind
메소드를 사용 하여 promise 체인에서 변수를 공유 할 수 있습니다 .
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
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
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);
});
해결책 :
'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);
(참고 :이 솔루션의 더 복잡하고 완전한 버전이 테스트이 예제 버전이 아니 버그가있을 수 있습니다.)
약속에 대해 배운 것은 반환 값으로 만 사용하여 가능한 경우 참조하지 않는 것 입니다. async / await 구문은 특히 실용적입니다. 모든 최신 브라우저와 노드는 지원합니다. https://caniuse.com/#feat=async-functions 는 간단한 동작이며 코드는 동기 코드를 읽는 것입니다. 잊어 버리십시오 ...
약속을 참조해야하는 경우는 독립적 / 비 관련 장소에서 생성 및 해결이 발생하는 경우입니다. 그래서 대신 인공적인 연결과 아마도 "먼"약속을 해결하기위한 이벤트 리스너 일 것입니다. 저는 약속을 Deferred로 노출하는 것을 선호합니다. 다음 코드는 유효한 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;
}());
내 타이프 펼쳐 프로젝트로 번역 :
더 복잡한 경우에는 테스트 및 입력없이 작은 약속 유틸리티를 자주 사용합니다. p-map은 여러 번 유용했습니다. 나는 대부분의 사용 사례를 다루었다고 생각합니다.
https://github.com/sindresorhus?utf8=%E2%9C%93&tab=repositories&q=promise&type=source&language=
'ProgramingTip' 카테고리의 다른 글
Java에서 바이트 배열을 16 진수로 변환하는 방법은 무엇입니까? (0) | 2020.10.03 |
---|---|
Android 개발 도구 v. 23으로 Eclipse 업데이트 (0) | 2020.10.03 |
간단한 DI 코드가 아닌 IoC 컨테이너가 필요한 이유는 무엇입니까? (0) | 2020.10.03 |
grep은 검색 할 수있는 패턴과 일치하는 단어 만 표시 할 수 있습니까? (0) | 2020.10.03 |
MySQL 쿼리 GROUP BY 일 / 월 / 년 (0) | 2020.10.03 |