함수를 다시 시작하기 전에 JavaScript Promise가 해결되기를 기다리는 방법은 무엇입니까?


108

단위 테스트를하고 있습니다. 테스트 프레임 워크는 페이지를 iFrame에로드 한 다음 해당 페이지에 대해 어설 션을 실행합니다. 각 테스트가 시작되기 전에 를 호출 Promise할 iFrame의 onload이벤트를 resolve()설정하고 iFrame의을 설정 src하고 promise를 반환 하는를 만듭니다.

따라서을 호출 loadUrl(url).then(myFunc)하면 무엇이든지 실행하기 전에 페이지가로드 될 때까지 기다립니다 myFunc.

주로 DOM 변경을 허용하기 위해 (예 : 버튼 클릭을 흉내 내고 div가 숨기고 표시 될 때까지 기다릴 때까지) 내 테스트 (URL로드뿐만 아니라) 전체에서 이런 종류의 패턴을 사용합니다.

이 디자인의 단점은 몇 줄의 코드로 익명 함수를 계속 작성하고 있다는 것입니다. 또한 해결 방법 (QUnit assert.async())이 있지만 약속을 정의하는 테스트 함수는 약속이 실행되기 전에 완료됩니다.

Promise.NET의 .NET과 유사하게 해결 될 때까지 또는 대기 (블록 / 슬립) 에서 값을 가져 오는 방법이 있는지 궁금합니다 IAsyncResult.WaitHandle.WaitOne(). JavaScript가 단일 스레드라는 것을 알고 있지만 이것이 함수가 양보 할 수 없다는 것을 의미하지는 않기를 바랍니다.

본질적으로 올바른 순서로 결과를 뱉어 내기 위해 다음을 얻을 수있는 방법이 있습니까?

function kickOff() {
  return new Promise(function(resolve, reject) {
    $("#output").append("start");
    
    setTimeout(function() {
      resolve();
    }, 1000);
  }).then(function() {
    $("#output").append(" middle");
    return " end";
  });
};

function getResultFrom(promise) {
  // todo
  return " end";
}

var promise = kickOff();
var result = getResultFrom(promise);
$("#output").append(result);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output"></div>


추가 호출을 재사용 가능한 함수에 넣으면 필요에 따라 DRY를 수행 할 수 있습니다. 다목적 핸들러를 만들 수도 있습니다. 이것 에 의해 then () 호출에 피드를 .then(fnAppend.bind(myDiv))제공 할 수 있습니다. 예를 들어 anon을 크게 줄일 수 있습니다.
dandavis 2015 년

무엇으로 테스트하고 있습니까? 최신 브라우저이거나 BabelJS와 같은 도구를 사용하여 코드를 변환 할 수 있다면 확실히 가능합니다.
Benjamin Gruenbaum 2015 년

답변:


81

.NET의 IAsyncResult.WaitHandle.WaitOne ()과 유사하게 Promise에서 값을 가져 오거나 해결 될 때까지 대기 (차단 / 절전) 할 수있는 방법이 있는지 궁금합니다. 자바 스크립트가 단일 스레드라는 것을 알고 있지만 이것이 함수가 양보 할 수 없다는 것을 의미하지는 않기를 바랍니다.

브라우저에서 자바 스크립트의 현재 세대는없는 wait()또는 sleep()그 실행에 다른 일을 할 수 있습니다. 그래서 당신은 단순히 당신이 요구하는 것을 할 수 없습니다. 대신, 작업을 수행 한 다음 완료되면 호출하는 비동기 작업이 있습니다 (Promise를 사용했던 것처럼).

이것의 일부는 Javascript의 단일 스레드 성 때문입니다. 단일 스레드가 회전 중이면 해당 회전 스레드가 완료 될 때까지 다른 Javascript를 실행할 수 없습니다. ES6는 yield이와 같은 협력적인 트릭을 허용하는 생성기를 도입 했지만, 설치된 브라우저의 광범위한 견본에서 사용할 수있는 방법은 상당히 많습니다 (JS 엔진을 제어하는 ​​서버 측 개발에서 사용할 수 있습니다) 사용중인).


약속 기반 코드를주의 깊게 관리하면 많은 비동기 작업의 실행 순서를 제어 할 수 있습니다.

코드에서 달성하려는 순서를 정확히 이해하지 못했지만 기존 kickOff()함수를 사용하여 이와 같은 작업을 수행 한 다음 .then()호출 한 후 처리기를 연결할 수 있습니다.

function kickOff() {
  return new Promise(function(resolve, reject) {
    $("#output").append("start");

    setTimeout(function() {
      resolve();
    }, 1000);
  }).then(function() {
    $("#output").append(" middle");
    return " end";
  });
}

kickOff().then(function(result) {
    // use the result here
    $("#output").append(result);
});

다음과 같이 보증 된 순서로 출력을 반환합니다.

start
middle
end

2018 년 업데이트 (이 답변이 작성된 후 3 년) :

당신도 당신의 코드를 transpile 또는 지원 ES7 같은 기능을한다는 환경에서 코드를 실행하는 경우 asyncawait, 당신은 지금 사용할 수있는 await코드가 "표시"할 약속의 결과를 기다려야한다. 그것은 여전히 ​​약속을 가지고 발전하고 있습니다. 여전히 모든 Javascript를 차단하지는 않지만 더 친숙한 구문으로 순차적 작업을 작성할 수 있습니다.

ES6 방식 대신 :

someFunc().then(someFunc2).then(result => {
    // process result here
}).catch(err => {
    // process error here
});

다음과 같이 할 수 있습니다.

// returns a promise
async function wrapperFunc() {
    try {
        let r1 = await someFunc();
        let r2 = await someFunc2(r1);
        // now process r2
        return someValue;     // this will be the resolved value of the returned promise
    } catch(e) {
        console.log(e);
        throw e;      // let caller know the promise was rejected with this reason
    }
}

wrapperFunc().then(result => {
    // got final result
}).catch(err => {
    // got error
});

3
맞아요, 사용 then()은 제가하고있는 일입니다. 나는 function() { ... }항상 글을 쓰는 것을 좋아하지 않습니다 . 코드를 복잡하게 만듭니다.
dx_over_dt

3
@dfoverdx-Javascript의 비동기 코딩에는 항상 콜백이 포함되므로 항상 함수 (익명 또는 명명)를 정의해야합니다. 현재는 그럴 방법이 없습니다.
jfriend00

2
ES6 화살표 표기법을 사용하여 더 간결하게 만들 수 있습니다. (foo) => { ... }대신function(foo) { ... }
Rob H

1
@RobH 주석에 자주 추가하면 foo => single.expression(here)중괄호와 둥근 괄호를 제거하기 위해 쓸 수도 있습니다.
begie

3
@dfoverdx-대답 한 지 3 년 후, ES7 asyncawait구문을 node.js와 최신 브라우저에서 또는 트랜스 파일러를 통해 사용할 수있는 옵션으로 업데이트했습니다 .
jfriend00

16

ES2016을 사용하는 경우 사용할 수 있습니다 asyncawait과 같은 것을 할 :

(async () => {
  const data = await fetch(url)
  myFunc(data)
}())

ES2015를 사용하는 경우 Generators 를 사용할 수 있습니다 . 구문이 마음에 들지 않으면 여기에 설명 된 대로 async유틸리티 함수 를 사용하여 추상화 할 수 있습니다 .

ES5를 사용하는 경우 Bluebird 와 같은 라이브러리가 더 많은 제어를 제공하기를 원할 것입니다 .

마지막으로 런타임이 ES2015를 지원하는 경우 이미 실행 순서는 Fetch Injection을 사용하여 병렬 처리로 보존 될 수 있습니다 .


1
생성기 예제가 작동하지 않고 러너가 없습니다. ES8 async/을 트랜스 파일 할 수있을 때 더 이상 생성기를 권장하지 마십시오 await.
Bergi

3
의견을 보내 주셔서 감사합니다. Generator 예제를 제거하고 대신 관련 OSS 라이브러리가 포함 된보다 포괄적 인 Generator 자습서에 연결했습니다.
조쉬 Habdas

10
이것은 단순히 약속을 포장합니다. 비동기 함수는 실행할 때 아무것도 기다리지 않고 약속을 반환합니다. async/ awaitPromise.then.
rustyx

당신은 절대적으로 맞다 async는하지 않는 ... 기다리지 않습니다 산출 하여 await. ES2016 / ES7 예제는 여전히 실행할 수 없으므로 여기에 3 년 전에 작성한 몇 가지 코드 가 있습니다.
Josh Habdas

7

또 다른 옵션은 Promise.all을 사용하여 일련의 프라 미스가 해결 될 때까지 기다리는 것입니다. 아래 코드는 모든 약속이 해결 될 때까지 기다린 다음 모든 준비가 완료되면 결과를 처리하는 방법을 보여줍니다 (질문의 목적인 것처럼 보임). 그러나 start는 resolve를 호출하기 전에 해당 코드를 추가하는 것만으로 middle이 해결되기 전에 분명히 출력 할 수 있습니다.

function kickOff() {
  let start = new Promise((resolve, reject) => resolve("start"))
  let middle = new Promise((resolve, reject) => {
    setTimeout(() => resolve(" middle"), 1000)
  })
  let end = new Promise((resolve, reject) => resolve(" end"))

  Promise.all([start, middle, end]).then(results => {
    results.forEach(result => $("#output").append(result))
  })
}

kickOff()
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="output"></div>


1

수동으로 할 수 있습니다. (그게 좋은 해결책은 아니지만 ..) 값이 없을 while때까지 루프를 사용하십시오.result

kickOff().then(function(result) {
   while(true){
       if (result === undefined) continue;
       else {
            $("#output").append(result);
            return;
       }
   }
});

이것은 기다리는 동안 전체 CPU를 소비합니다.
Lukasz Guminski

이것은 끔찍한 솔루션입니다
JSON
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.