JavaScript 인터뷰 마스터하기 : 약속이란 무엇입니까?

Kabun의 사진 (CC BY NC SA 2.0)
“Master the JavaScript Interview”는 중급에서 상급 JavaScript 위치에 지원할 때 발생할 수있는 일반적인 질문에 대한 응시자를 준비하기 위해 설계된 일련의 게시물입니다. 실제 인터뷰에서 자주 사용하는 질문입니다.

약속이란 무엇입니까?

약속은 나중에 언젠가는 단일 값을 생성 할 수있는 개체입니다. 즉, 해결 된 값 또는 해결되지 않은 이유 (예 : 네트워크 오류가 발생 함). 약속은 가능한 3 가지 상태 중 하나 일 수 있습니다 : 이행, 거부 또는 보류 중. 약속 사용자는 이행 값 또는 거부 사유를 처리하기 위해 콜백을 첨부 할 수 있습니다.

약속은 간절합니다. 이는 약속 생성자가 호출 되 자마자 약속이 수행하는 모든 작업을 시작한다는 것을 의미합니다. 게으른 게 필요하면 관찰 가능 또는 작업을 확인하십시오.

불완전한 약속의 역사

1980 년대 초에 MultiLisp 및 Concurrent Prolog와 같은 언어로 약속과 미래의 초기 구현 (유사한 / 관련 아이디어)이 나타나기 시작했습니다. "약속"이라는 단어의 사용은 1988 년 Barbara Liskov와 Liuba Shrira에 의해 만들어졌습니다 [1].

JavaScript에서 처음으로 약속을 들었을 때 Node는 새로운 것이었고 커뮤니티는 비동기 동작을 처리하는 가장 좋은 방법을 논의했습니다. 커뮤니티는 한동안 약속을 실험했지만 결국 노드 표준 오류 우선 콜백에 정착했습니다.

거의 동시에 Dojo는 Deferred API를 통해 약속을 추가했습니다. 점점 더 많은 관심과 활동으로 인해 다양한 약속을보다 상호 운용 할 수 있도록 설계된 새로 구성된 Promises / A 사양이 만들어졌습니다.

jQuery의 비동기 동작은 약속을 중심으로 리팩토링되었습니다. jQuery의 약속 지원은 Dojo 's Deferred와 현저한 유사성을 보였으며 jQuery의 엄청난 인기로 인해 한동안 JavaScript에서 가장 일반적으로 사용되는 약속 구현이되었습니다. 그러나 사람들이 약속 위에 도구를 구축하기 위해 믿고 있던 두 가지 채널 (완료 / 거부) 체인 동작 및 예외 관리는 지원하지 않았습니다.

이러한 약점에도 불구하고 jQuery는 공식적으로 JavaScript 약속을 주류로 만들었고 Q, When 및 Bluebird와 같은 더 나은 독립형 약속 라이브러리가 매우 인기를 얻었습니다. jQuery의 구현 비 호환성은 Promise / A + 사양으로 다시 작성되고 브랜드가 변경되는 약속 사양에서 몇 가지 중요한 설명을 유발했습니다.

ES6는 Promises / A +를 준수하는 Promise를 전 세계에 도입했으며, WHATWG Fetch 사양 및 Async Functions 표준 (이 글을 쓰는 시점의 3 단계 초안)과 같은 매우 중요한 API가 새로운 표준 Promise 지원 위에 구축되었습니다.

여기에 설명 된 약속은 ECMAScript 표준 Promise 구현에 중점을두고 Promises / A + 사양과 호환되는 약속입니다.

약속의 작동 방식

promise는 비동기 함수에서 동 기적으로 반환 될 수있는 객체입니다. 가능한 3 가지 상태 중 하나입니다.

  • 이행 : onFulfilled ()가 호출됩니다 (예 : resolve ()가 호출 됨)
  • 거부 됨 : onRejected ()가 호출됩니다 (예 : reject ()가 호출 됨)
  • 보류 중 : 아직 이행 또는 거부되지 않은

약속이 보류 중이 아닌 경우 해결됩니다 (해결되었거나 거부 된 경우). 때때로 사람들은 해결되지 않고 해결 된 것을 사용합니다.

일단 확정되면 약속을 확정 할 수 없습니다. resolve () 또는 reject ()를 다시 호출해도 효과가 없습니다. 확정 된 약속의 불변성은 중요한 특징입니다.

기본 JavaScript 약속은 약속 상태를 노출하지 않습니다. 대신 약속을 블랙 박스로 취급해야합니다. 약속 작성을 담당하는 기능 만 약속 상태를 알고 있거나 해결하거나 거부 할 수 있습니다.

다음은 지정된 시간 지연 후에 해결 될 약속을 반환하는 함수입니다.

wait (3000) 호출은 3000ms (3 초) 기다린 다음 'Hello!'를 기록합니다. 모든 스펙 호환 약속은 해결되거나 거부 된 값을 취할 수있는 핸들러를 전달하는 데 사용하는 .then () 메소드를 정의합니다.

ES6 promise 생성자는 기능을 수행합니다. 이 함수는 resolve () 및 reject ()라는 두 개의 매개 변수를 사용합니다. 위의 예에서는 resolve () 만 사용하고 있으므로 매개 변수 목록에서 reject ()를 남겨 두었습니다. 그런 다음 setTimeout ()을 호출하여 지연을 만들고 완료되면 resolve ()를 호출합니다.

선택적으로 resolve () 또는 reject () 값을 사용하여 .then ()이 첨부 된 콜백 함수로 전달됩니다.

값으로 거부 ()를 거부하면 항상 Error 객체를 전달합니다. 일반적으로 가능한 두 가지 해결 상태, 즉 정상적인 행복 경로 또는 예외 – 정상적인 행복 경로의 발생을 막는 것입니다. Error 객체를 전달하면 명시 적입니다.

중요한 약속 규칙

약속 표준은 Promises / A + 사양 커뮤니티에 의해 정의되었습니다. JavaScript 표준 ECMAScript 약속을 포함하여 표준을 준수하는 많은 구현이 있습니다.

사양을 따르는 약속은 특정 규칙 집합을 따라야합니다.

  • 약속 또는 "thenable"은 표준 호환 .then () 메소드를 제공하는 객체입니다.
  • 보류중인 약속은 이행 또는 거부 상태로 전환 될 수 있습니다.
  • 이행 또는 거부 된 약속은 확정되며 다른 국가로 이행해서는 안됩니다.
  • 약속이 확정되면 값을 가져야합니다 (정의되지 않을 수 있음). 그 값은 바뀌지 않아야합니다.

이러한 맥락에서의 변화는 동일성 (===) 비교를 의미합니다. 충족 된 값으로 개체를 사용할 수 있으며 개체 속성이 변경 될 수 있습니다.

모든 약속은 다음 서명을 가진 .then () 메소드를 제공해야합니다.

promise.then (
  onFulfilled ?: 기능,
  onRejected ?: 기능
) => 약속

.then () 메소드는 다음 규칙을 준수해야합니다.

  • onFulfilled () 및 onRejected ()는 모두 선택 사항입니다.
  • 제공된 인수가 함수가 아닌 경우 무시해야합니다.
  • onFulfilled ()는 약속이 이행 된 후 약속의 가치를 첫 번째 인수로하여 호출됩니다.
  • onRejected ()는 약속이 거부 된 후 거부 이유가 첫 번째 인수로 호출됩니다. 유효한 JavaScript 값일 수 있지만 거부는 기본적으로 예외와 동의어이므로 Error 객체를 사용하는 것이 좋습니다.
  • onFulfilled () 또는 onRejected ()를 두 번 이상 호출 할 수 없습니다.
  • .then ()은 같은 약속으로 여러 번 호출 될 수 있습니다. 즉, 약속은 콜백을 집계하는 데 사용될 수 있습니다.
  • .then ()은 새로운 약속 promise2를 반환해야합니다.
  • onFulfilled () 또는 onRejected ()가 값 x를 반환하고 x가 약속이면 promise2는 x와 동일한 상태 및 값을 가정합니다. 그렇지 않으면 promise2는 x 값으로 이행됩니다.
  • onFulfilled 또는 onRejected에서 예외 e가 발생하면 promise2는 e를 이유로 거부해야합니다.
  • onFulfilled가 함수가 아니고 promise1이 충족 된 경우 promise2와 같은 값으로 promise2를 이행해야합니다.
  • onRejected가 함수가 아니고 promise1이 거부 된 경우 promise1과 같은 이유로 promise2가 거부되어야합니다.

약속 체인

.then ()은 항상 새로운 약속을 반환하므로 오류 처리 방법 및 위치를 정확하게 제어하여 약속을 연결할 수 있습니다. 약속을 통해 일반 동기 코드의 시도 / 캐치 동작을 모방 할 수 있습니다.

동기 코드와 마찬가지로 체인을 연결하면 일련의 시퀀스가 ​​실행됩니다. 다시 말해, 다음을 수행 할 수 있습니다.

가져 오기 (url)
  .then (프로세스)
  . (저장)
  .catch (handleErrors)
;

각 함수, fetch (), process () 및 save ()가 약속을 리턴한다고 가정하면, process ()는 시작하기 전에 fetch ()가 완료 될 때까지 대기하고 save ()는 시작하기 전에 process ()가 완료 될 때까지 대기합니다. handleErrors ()는 이전 약속 중 하나라도 거부하는 경우에만 실행됩니다.

거부가 여러 개인 복잡한 약속 체인의 예는 다음과 같습니다.

오류 처리

약속에는 성공과 오류 처리기가 모두 있으며 다음과 같은 코드를 보는 것이 일반적입니다.

save (). then (
  handleSuccess,
  handleError
);

그러나 handleSuccess ()에서 오류가 발생하면 어떻게됩니까? .then ()에서 반환 된 약속은 거부되지만 거부 할 부분은 없습니다. 즉 앱의 오류가 삼킨다는 의미입니다. 죄송합니다!

따라서 일부 사람들은 위의 코드를 안티 패턴으로 간주하고 대신 다음을 권장합니다.

구하다()
  .then (handleSuccess)
  .catch (handleError)
;

그 차이는 미묘하지만 중요합니다. 첫 번째 예에서는 save () 작업에서 발생하는 오류가 발생하지만 handleSuccess () 함수에서 발생하는 오류가 발생합니다.

.catch ()가 없으면 성공 핸들러의 오류가 포착되지 않습니다.

두 번째 예에서 .catch ()는 save () 또는 handleSuccess ()의 거부를 처리합니다.

.catch ()를 사용하면 두 가지 오류 소스가 모두 처리됩니다. (다이어그램 소스)

물론 save () 오류는 네트워킹 오류 일 수 있지만 handleSuccess () 오류는 개발자가 특정 상태 코드 처리를 잊었 기 때문일 수 있습니다. 다르게 처리하려면 어떻게해야합니까? 둘 다 처리하도록 선택할 수 있습니다.

구하다()
  .그때(
    handleSuccess,
    handleNetworkError
  )
  .catch (handleProgrammerError)
;

무엇을 좋아하든 모든 약속 체인을 .catch ()로 끝내는 것이 좋습니다. 반복 할 가치가 있습니다.

모든 약속 체인을 .catch ()로 끝내는 것이 좋습니다.

약속을 어떻게 취소합니까?

새로운 약속 사용자가 가장 먼저 생각하는 것 중 하나는 약속을 취소하는 방법입니다. 아이디어는 다음과 같습니다. 이유가 "취소됨"인 약속을 거부하십시오. "정상"오류와 다르게 처리해야하는 경우 오류 처리기에서 분기를 수행하십시오.

약속 취소를 할 때 사람들이 저지르는 실수는 다음과 같습니다.

약속에 .cancel () 추가

.cancel ()을 추가하면 약속이 비표준이되지만 약속의 또 다른 규칙을 위반하게됩니다. 약속을 만드는 함수 만 약속을 해결, 거부 또는 취소 할 수 있어야합니다. 그것이 노출되면 캡슐화가 깨지고 사람들이 알지 못하는 곳에서 약속을 다루는 코드를 작성하도록 권장합니다. 스파게티와 깨진 약속을 피하십시오.

정리 잊어 버린

일부 영리한 사람들은 Promise.race ()를 취소 메커니즘으로 사용하는 방법이 있다는 것을 알아 냈습니다. 문제는 취소 제어가 약속을 만드는 기능에서 가져옵니다. 이는 약속 시간을 지우거나 데이터 참조를 지우는 등의 메모리 정리와 같은 적절한 정리 작업을 수행 할 수있는 유일한 장소입니다.

거부 된 취소 약속 처리를 잊어 버림

약속 거부를 잊어 버렸을 때 Chrome에서 콘솔 전체에 경고 메시지를 표시한다는 것을 알고 계십니까? 죄송합니다!

지나치게 복잡한

취소를위한 철회 된 TC39 제안은 취소를위한 별도의 메시징 채널을 제안했습니다. 또한 취소 토큰이라는 새로운 개념을 사용했습니다. 제 생각에, 솔루션은 약속 사양을 크게 부 풀렸을 것이며, 추측이 직접 지원하지 않는 유일한 기능은 거절과 취소의 분리뿐입니다. 이는 IMO가 처음에는 필요하지 않습니다.

예외 또는 취소 여부에 따라 전환을 원하십니까? 네 그럼요. 이것이 약속의 일입니까? 제 생각에는 그렇지 않습니다.

약속 취소 재검토

일반적으로 약속을 만들 때 약속을 해결 / 거부 / 취소하는 방법을 결정하는 데 필요한 모든 정보를 전달합니다. 그렇게하면 약속에 .cancel () 메서드가 필요하지 않습니다. 약속 작성 시간에 취소할지 여부를 어떻게 알 수 있는지 궁금 할 것입니다.

"취소 여부를 아직 모른다면 약속을 만들 때 무엇을 전달해야하는지 어떻게 알 수 있습니까?"

미래의 잠재적 가치를 견딜 수있는 어떤 종류의 물건 만 있다면… 오, 잠깐만.

취소 여부를 나타 내기 위해 우리가 전달하는 가치는 약속 그 자체가 될 수 있습니다. 그 모습은 다음과 같습니다.

기본 매개 변수 할당을 사용하여 기본적으로 취소하지 않도록 지시합니다. 이는 cancel 매개 변수를 편리하게 선택적으로 만듭니다. 그런 다음 이전과 같이 시간 초과를 설정했지만 이번에는 시간 초과 ID를 캡처하여 나중에 지울 수 있습니다.

우리는 cancel.then () 메소드를 사용하여 취소 및 리소스 정리를 처리합니다. 약속이 해결되기 전에 취소 된 경우에만 실행됩니다. 너무 늦게 취소하면 기회를 놓친 것입니다. 그 기차는 역을 떠났습니다.

참고 : noop () 함수가 무엇인지 궁금 할 것입니다. noop이라는 단어는 no-op를 나타내며, 아무것도하지 않는 기능을 의미합니다. 이것이 없으면 V8은 경고를 처리합니다. UnhandledPromiseRejectionWarning : 처리되지 않은 약속 거부. 처리기가 noop () 인 경우에도 약속 거부를 항상 처리하는 것이 좋습니다.

약속 약속 취소

wait () 타이머에는 좋지만, 기억해야 할 모든 것을 캡슐화하기 위해이 아이디어를 더 추상화 할 수 있습니다.

  1. 기본적으로 취소 약속을 거부합니다. 취소 약속이 전달되지 않으면 취소하거나 오류를 발생시키지 않습니다.
  2. 취소를 거부 할 때 정리를 수행해야합니다.
  3. onCancel 정리 자체에서 오류가 발생할 수 있으며 해당 오류도 처리해야합니다. (위의 대기 예제에서는 오류 처리가 생략되어 잊어 버리기가 쉽습니다.)

약속을 포장하는 데 사용할 수있는 취소 가능한 약속 유틸리티를 만들어 봅시다. 예를 들어, 네트워크 요청 등을 처리하려면 ... 서명은 다음과 같습니다.

speculation (fn : SpecFunction, shouldCancel : Promise) => 약속

SpecFunction은 Promise 생성자에 전달할 함수와 같지만 onCancel () 처리기가 필요합니다.

SpecFunction (해결 : 기능, 거부 : 기능, onCancel : 기능) => 무효

이 예제는 작동 방식을 간략하게 설명하기위한 예시 일뿐입니다. 고려해야 할 몇 가지 다른 경우가 있습니다. 예를 들어,이 버전에서 handleCancel은 약속이 이미 확정 된 후에 취소하면 호출됩니다.

오픈 소스 라이브러리 인 Speculation으로 다루는 엣지 케이스를 사용하여 유지 관리 된 프로덕션 버전을 구현했습니다.

개선 된 라이브러리 추상화를 사용하여 이전부터 취소 가능한 wait () 유틸리티를 다시 작성해 보겠습니다. 먼저 설치 추측 :

npm install-저장 추측

이제 가져 와서 사용할 수 있습니다.

noop ()에 대해 걱정할 필요가없고 onCancel (), 함수 또는 기타 엣지 케이스에서 오류를 잡을 필요가 없기 때문에 약간 단순화합니다. 이러한 세부 사항은 speculation ()에 의해 추상화되었습니다. 그것을 확인하고 실제 프로젝트에서 자유롭게 사용하십시오.

기본 JS 약속의 추가 사항

기본 Promise 개체에는 다음과 같은 추가 항목이 있습니다.

  • Promise.reject ()는 거부 된 약속을 반환합니다.
  • Promise.resolve ()는 해결 된 약속을 반환합니다.
  • Promise.race ()는 배열 (또는 이터 러블)을 가져 와서 이터 러블에서 첫 번째로 해결 된 약속의 값으로 해결되거나 첫 번째로 거부 된 약속의 이유로 거부되는 약속을 반환합니다.
  • Promise.all ()은 배열 (또는 모든 반복 가능)을 가져 와서 반복 가능한 인수의 모든 약속이 해결되거나 거부 된 첫 번째 전달 된 약속의 이유로 거부되는 약속을 반환합니다.

결론

대부분의 최신 아약스 요청에 사용되는 WHATWG Fetch 표준과 비동기 코드를 동기식으로 만드는 데 사용되는 Async Functions 표준을 포함하여 약속은 JavaScript에서 여러 관용구의 필수 부분이되었습니다.

비동기 함수는이 글을 쓰는 시점에서 3 단계이지만 JavaScript에서 비동기 프로그래밍을위한 매우 널리 사용되고 널리 사용되는 솔루션이 될 것으로 예상됩니다. 이는 약속을 이해하는 학습이 JavaScript에 더욱 중요하다는 것을 의미합니다 가까운 장래에 개발자.

예를 들어 Redux를 사용하는 경우 Redux-saga : Redux의 부작용을 관리하는 데 사용되는 라이브러리 인 문서 전체의 비동기 기능에 의존하는 라이브러리를 확인하는 것이 좋습니다.

경험이 풍부한 약속 사용자조차도 약속이 무엇인지, 어떻게 작동하는지, 그리고 이것을 읽은 후 더 잘 사용하는 방법을 더 잘 이해하기를 바랍니다.

시리즈 둘러보기

  • 폐쇄 란 무엇입니까?
  • 계급과 프로토 타입 상속의 차이점은 무엇입니까?
  • 순수한 기능이란 무엇입니까?
  • 기능 구성이란 무엇입니까?
  • 기능적 프로그래밍이란 무엇입니까?
  • 약속이란 무엇입니까?
  • 소프트 스킬
  1. 바바라리스 코프; Liuba Shrira (1988). “약속 : 분산 시스템에서 효율적인 비동기 프로 시저 호출을위한 언어 지원”. 프로그래밍 언어 설계 및 구현에 관한 SIGPLAN 88 회의의 진행; 미국 조지 아주 애틀랜타, 260-267 쪽. ISBN 0–89791–269–1 (ACM에서 공개). 또한 ACM SIGPLAN Notices, Volume 23, Issue 7, 1988 년 7 월에 출판되었습니다.
EricElliottJS.com에서 무료 레슨을 시작하십시오

Eric Elliott는 "Composing Software"및 "JavaScript Applications 프로그래밍"책의 저자입니다. EricElliottJS.com과 DevAnywhere.io의 공동 창립자로서 개발자에게 필수 소프트웨어 개발 기술을 가르칩니다. 그는 암호화 프로젝트를위한 개발 팀을 구성하고 조언하며 Adobe Systems, Zumba Fitness, The Wall StreetJournal, ESPN, BBC 및 Usher, Frank Ocean, Metallica 등을 포함한 최고의 레코딩 아티스트를위한 소프트웨어 경험에 기여했습니다.

그는 세상에서 가장 아름다운 여성과 함께 외진 생활을 즐깁니다.