자바 스크립트는 호기심을 약속합니다


96

이 promise를 호출하면 출력이 함수 호출 순서와 일치하지 않습니다. 는 .then전과 제공 .catch과 약속은 비록 .then후에 호출되고 있었다. 그 이유는 무엇입니까?

const verifier = (a, b) =>
  new Promise((resolve, reject) => (a > b ? resolve(true) : reject(false)));

verifier(3, 4)
  .then((response) => console.log("response: ", response))
  .catch((error) => console.log("error: ", error));

verifier(5, 4)
  .then((response) => console.log("response: ", response))
  .catch((error) => console.log("error: ", error));

산출

node promises.js
response: true
error: false

34
독립적 인 약속 체인 사이의 타이밍에 의존해서는 안됩니다.
Bergi

답변:


136

이것은 바닥에 도달하기위한 일종의 멋진 질문입니다.

이렇게하면 :

verifier(3,4).then(...)

새로 거부 된 promise .catch()가 다음 핸들러를 실행하기 전에 이벤트 루프로 다시 돌아 가야하는 새로운 promise를 반환 합니다. 추가주기는 다음 시퀀스를 제공합니다.

verifier(5,4).then(...)

첫 번째 핸들러가 큐에 들어가고 항목이 FIFO 순서로 큐에서 실행 되기 전에 이미 큐에 있었기 때문에 .then()이전 라인보다 먼저 핸들러 를 실행할 기회 ..catch().catch()


.then(f1, f2)대신에 양식 을 사용하면 .then().catch()추가 약속이 없으므로 추가 틱이 필요하지 않기 때문에 예상 할 때 실행됩니다.

const verifier = (a, b) =>
  new Promise((resolve, reject) => (a > b ? resolve(true) : reject(false)));

verifier(3, 4)
  .then((response) => console.log("response (3,4): ", response),
        (error) => console.log("error (3,4): ", error)
  );

verifier(5, 4)
  .then((response) => console.log("response (5,4): ", response))
  .catch((error) => console.log("error (5,4): ", error));

또한 모든 메시지에 레이블을 지정하여 어떤 verifier()호출에서 오는지 확인할 수 있으므로 출력을 훨씬 쉽게 읽을 수 있습니다.


약속 콜백 주문에 대한 ES6 사양 및 자세한 설명

ES6 사양은 약속 "작업"( .then()또는 에서 콜백을 호출 .catch()할 때)이 작업 대기열에 삽입되는시기를 기준으로 FIFO 순서로 실행 됨을 알려줍니다 . FIFO라는 이름은 구체적으로 지정하지 않지만 새 작업이 대기열 끝에 삽입되고 작업이 대기열의 처음부터 실행되도록 지정합니다. 이는 FIFO 주문을 구현합니다.

PerformPromiseThen (에서 콜백을 실행 함 .then())은 확인 또는 거부 처리기가 실제로 실행되도록 예약되는 방법 인 EnqueueJob이어 집니다. EnqueueJob은 보류중인 작업이 작업 대기열의 뒤쪽에 추가되도록 지정합니다. 그런 다음 NextJob 작업은 대기열의 앞쪽에서 항목을 가져옵니다. 이렇게하면 Promise 작업 대기열에서 작업을 처리 할 때 FIFO 순서가 보장됩니다.

따라서 원래 질문의 예에서 verifier(3,4)promise에 대한 콜백 과 verifier(5,4)Promise가 실행 된 순서대로 작업 대기열에 삽입 된 Promise는 두 원래 promise가 모두 수행 되었기 때문입니다. 그런 다음 인터프리터가 이벤트 루프로 돌아 가면 먼저 verifier(3,4)작업을 선택합니다 . 그 약속은 거부되었으며 verifier(3,4).then(...). 따라서 verifier(3,4).then(...)반환 된 promise를 거부 하고 verifier(3,4).then(...).catch(...)핸들러가 jobQueue에 삽입됩니다.

그런 다음 이벤트 루프로 돌아가고 jobQueue에서 가져 오는 다음 작업이 verifier(5, 4)작업입니다. 해결 된 약속과 해결 처리기가 있으므로 해당 처리기를 호출합니다. 이로 인해 response (5,4):출력이 표시됩니다.

그런 다음 이벤트 루프로 돌아가고 jobQueue에서 가져 오는 다음 작업은 해당 verifier(3,4).then(...).catch(...)작업을 실행하는 작업이며 이로 인해 error (3,4)출력이 표시됩니다.

때문이다 .catch()1 체인이보다는 그 체인에 한 약속 수준 깊은 .then()신고 순서를 일으키는 원인이되는 2 체인이다. 그리고 약속 체인이 동기식이 아닌 FIFO 순서로 작업 대기열을 통해 한 수준에서 다음 수준으로 이동하기 때문입니다.


이 수준의 일정 세부 정보 사용에 대한 일반 권장 사항

참고로, 일반적으로이 수준의 상세한 타이밍 지식에 의존하지 않는 코드를 작성하려고합니다. 호기심이 많고 때로는 이해하는 것이 유용하지만 코드에 대한 단순한 변경으로 인해 상대적인 타이밍이 변경 될 수 있으므로 깨지기 쉬운 코드입니다. 따라서 이와 같은 두 체인간에 타이밍이 중요하다면이 수준의 자세한 이해에 의존하는 것보다 원하는 방식으로 타이밍을 강제하는 방식으로 코드를 작성하는 것이 좋습니다.


더 구체적으로 말하자면,이 정확한 동작은이를 구현 세부 사항으로 만드는 promise의 사양 어디에도 문서화되어 있지 않습니다. 인터프리터간에 (예 : Node.js 대 Edge 대 Firefox) 또는 인터프리터 버전 (예 : Node 12 대 Node 14)간에 다른 동작이 발생할 수 있습니다. 사양은 단지 약속이 zalgo 코드를 피하기 위해 비동기 적으로 처리된다는 것을 말합니다 (IMHO는 잠재적 인 비동기 코드의 타이밍에 의존하고 싶어하는 이와 같은 질문을하는 사람들이 동기를 부여했기 때문에 BTW를 잘못 안내했습니다)
slebetman

@slebetman-별도의 promise의 promise 콜백이 대기열에 삽입 된 시점을 기준으로 FIFO라고하며 다음 틱까지 실행할 수 없다는 것이 문서화되어 있지 않습니까? FIFO 주문은 .then()이 주문으로 이어지는 미래의 틱에서 비동기 적으로 해결 / 거부 해야하는 새로운 약속을 반환 해야하기 때문에 여기서 필요한 모든 것 같습니다 . 경쟁 콜백의 FIFO 순서를 사용하지 않는 구현을 알고 있습니까?
jfriend00

3
@slebetman Promises / A +는 그것을 지정하지 않습니다. ES6는 그것을 지정합니다. (ES11은의 동작을 변경했습니다 await).
Bergi

대기열 순서에 대한 ES6 사양에서. PerformPromiseThen이어질 것으로 EnqueueJob하는 핸들러가 호출 될 예정 도착 거부 방법 해결 또는이다. EnqueueJob은 보류중인 작업이 작업 대기열의 뒤쪽에 추가되도록 지정합니다. 그런 다음 NextJob 작업은 대기열의 앞쪽에서 항목을 가져옵니다. 이렇게하면 Promise 작업 대기열에서 FIFO 순서가 보장됩니다.
jfriend00

@Bergi ES11의 변경 사항은 무엇입니까 await? 링크면 충분합니다. 감사!!
Pedro A

49

Promise.resolve()
  .then(() => console.log('a1'))
  .then(() => console.log('a2'))
  .then(() => console.log('a3'))
Promise.resolve()
  .then(() => console.log('b1'))
  .then(() => console.log('b2'))
  .then(() => console.log('b3'))

출력 a1, a2, a3, b1, b2, b3 대신 같은 이유로 인해 a1, b1, a2, b2, a3, b3이 표시됩니다. 매번 약속을 반환하고 이벤트 루프의 끝으로 이동합니다. 열. 그래서 우리는이 "약속 경주"를 볼 수 있습니다. 중첩 된 약속이있을 때도 마찬가지입니다.

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