JavaScript에서 약속한 문제

최근 노드에서 많은 시간을 보냈는데 약속과 관련된 세 가지 반복되는 문제에 부딪혔다.
  • Promises에는 위험 코드 사용을 권장하는 API가 있음
  • Promises에는 데이터를 안전하게 처리할 수 있는 편리한 API가 없습니다.
  • 약속 혼합 거절 약속과 의외의 운행 시 이상
  • await 문법은 언어에 첨가하기에 적합하고 이 문제 해결 방안의 일부이지만, 그 가치는 읽기 가능성을 높이고 원시 호출 창고에서 제어를 유지하는 것이다. (즉 되돌아오는 것을 허용하는 것). 다음 두 문제와 상관없이, 때로는 첫 번째 문제를 완화시킬 수 있다.

    Promises에는 위험 코드를 마음대로 작성하도록 권장하는 API가 있습니다.


    사용자를 저장하는 예를 들면 다음과 같습니다.
    // Promises (without using await)
    // Casually dangerous code
    const handleSave = rawUserData => {
      saveUser(rawUserData)
        .then(user => createToast(`User ${displayName(user)} has been created`))
        .catch(err => createToast(`User could not be saved`));
    };
    
    이 코드는 읽을 수 있고 명확하게 보인다. 성공과 실패의 경로를 명확하게 정의했다.
    그러나 명확한 표시를 시도하는 동시에 우리는 catchsaveUser 요청에 추가할 뿐만 아니라 성공 경로에도 추가했다.따라서 then 버퍼링 (예를 들어 디스플레이 Name 함수 버퍼링) 을 하면, 저장된 사용자도 저장하지 않았다는 알림을 받을 것입니다.

    One might think, lets switch the order of the then/catch, so that the catch is attached to the saveUser call directly. This introduces another issue, we'll look at further below (as the third issue).


    wait를 사용하는 것이 반드시 도움이 되는 것은 아니다.API를 올바르게 사용하는 것은 알 수 없으며 블록 범위의 제한으로 인해 위와 같이 API를 쉽게 작성할 수 있고 아름답게 만들 수 있습니다.
    // Promises with Async/Await doesn't necessarily help
    // Casually dangerous code
    const handleSave = async rawUserData => {
      try {
        const user = await saveUser(rawUserData);
        createToast(`User ${displayName(user) has been created`);
      } catch {
        createToast(`User could not be saved`));
      }
    };
    
    블록의 역할 영역 때문에try에createToast 줄을 포함하는 것이 더 편리하지만, 이 코드는 위의 문제와 같습니다.
    본 기기에서 약속한 책임 있는 재구성은 더 나쁘거나 추하거나 나쁘거나 복잡해 보입니다.먼저 사용하지 않는 경우await를 살펴보자.
    사용하지 않는 경우await는 정확한 순서대로 두 개의 익명 함수를 사용하십시오(오류 함수 우선? 성공 함수 우선?)then에 전달해야 합니다. 이 느낌은 현식catch 블록을 사용하는 것처럼 조리가 없습니다.
    // Promises done responsibly _look_ worse/ugly/bad/complicated :(
    const handleSave = rawUserData => {
      saveUser(rawUserData)
        .then(
          user => createToast(`User ${displayName(user)} has been created`),
          err => createToast(`User could not be saved`));
        );
    };
    
    그 자체가 나쁜 API가 아니라는 것을 명확히 해야 한다.그러나 개발자로서의 정당한 의도를 감안하면 하나의 then와 두 개의 리셋 함수가 아닌 모든 호출에 하나의 명명 함수를 사용하는 유혹이 있다.책임 있는 코드는 위험한 코드처럼 명확하고 읽을 수 없다. API를 오용하는 것은 매우 위험하다. 동시에 더욱 명확하고 읽을 수 있다고 느낀다!async/await를 사용하여 책임감 있는 재구성을 하는 것은 더욱 틀렸다/추하다/나쁘다/복잡하다.더 높은 범위 내에서 변수를 정의해야 한다는 것은 나쁜 통제 흐름처럼 느껴진다.마치 우리가 언어와 맞서고 있는 것 같다.
    // Promises done responsibly _look_ worse/ugly/bad/complicated :(
    const handleSave = async rawUserData => {
      let user;
      try {
        user = await saveUser(rawUserData);
      } catch {
        createToast(`User could not be saved`));
      }
      createToast(`User ${displayName(user)} has been created`);
    };
    
    위의 코드가 심지어 정확하지 않으니 주의해라.우리는 catch 블록에서 되돌아와야 한다. (이것은 내가 가능한 한 피해야 한다. 왜냐하면 그것은 흐름을 더 혼동할 수 있기 때문이다. 특히finally가 있다면) 블록에서 시도하거나 if (user) { /*...*/ } 블록에서 모든 내용을 포장해서 다른 블록을 만들 수 있기 때문이다.우리가 위로 기어오르는 것 같아.
    또 주의해야 할 것은 API도 직관적이지 않다는 것이다. (그러나 이번에는 반대다.)여러 개의 thens를 연결할 때.
    비록 위의 예는 위험하다. catch 는 '루트' 비동기 호출 (HTTP 요청) 에 추가되어야 하기 때문에 catch 최신 비동기 호출과 관련이 있다고 긴 체인으로 생각하면 위험하다.
    (뿌리 약속도 최근의 약속도 부착되지 않는다. 그 전의 전체 체인에 부착된다.)
    예를 들면 다음과 같습니다.
    // Casually dangerous code
    const userPostHandler = rawUserData => {
      saveUser(rawUserData)
        .then(sendWelcomeEmail)
        .catch(queueWelcomeEmailForLaterAttempt)
    };
    
    책임자에 비해 보기와 읽기가 깨끗하다.
    // Promises done responsibly _look_ worse/ugly/bad/complicated :(
    const userPostHandler = rawUserData => {
      saveUser(rawUserData)
        .then(user =>
          sendWelcomeEmail(user)
            .catch(queueWelcomeEmailForLaterAttempt)
        );
    };
    
    다음 예제에서 API가 위험하다는 마지막 방법을 살펴보겠습니다. 사용자를 만들 수 없으면 로그를 추가하십시오.
    // Dangerous code
    const userPostHandler = rawUserData => {
      saveUser(rawUserData)
        .catch(writeIssueToLog)
        .then(sendWelcomeEmail)
        .catch(queueWelcomeEmailForLaterAttempt)
    };
    
    사용자가 저장에 실패하면, 우리는 문제를 로그에 쓰기를 희망합니다.
    단, 우리의 포획은 다시 던지거나 명확하게 거부되지 않기 때문에, 해결된 약속을 되돌려줍니다. 따라서 다음 약속 (send Welcome Email) 은 실행될 것입니다. 사용자가 없기 때문에, 포획은 해결된 약속을 되돌려줍니다. 존재하지 않는 사용자를 위해 줄을 서는 전자 우편을 만들 것입니다.
    자유로운 promise API는 예기치 못한 복구를 쉽고 매끄럽고 우아하게 만듭니다.
    마찬가지로 복구는 엉망으로 보인다.
    // Promises done responsibly _look_ worse/ugly/bad/complicated :(
    const userPostHandler = rawUserData => {
      saveUser(rawUserData)
        .then(
          writeIssueToLog,
          user =>
              sendWelcomeEmail(user)
                .catch(queueWelcomeEmailForLaterAttempt)
          );
    };
    
    이 절이 끝났을 때, 우리는promise의 API가 오류를 처리하는 동시에 원활해 보이는 것을 보았다. 이것은 우연한 상황에서 위험하다. 이 두 가지 원인은 모두 읽을 수 있고 편리하기 때문이다. then 에서 단독으로 포획할 수 있다. (즉, 현식catch 함수를 사용한다. 만약에 하나의 체인에서 '루트' 약속에서 온 오류뿐만 아니라, 최신 약속에서 온 오류도 포함하고, 체인에서 온 모든 약속에서 온 오류도 포함한다.)무의식중에 오류를 회복하는 것을 촉진한다.
    비록 async 조작부호를 추가하는 것이 도움이 되지만, 이것은try 범위 내에서 이루어진 것이다. 정확한 코드를 갈라지게 하고, 무책임한 코드를 (try에 너무 많이 놓아서) 더욱 깨끗하고 매끄럽게 보인다.
    나는 최소한 책임 있는 행위의 미관성과 가독성을 최적화할 수 있는 API를 더 좋아한다. (이 언어를 사용함으로써) 무책임하거나 임의로 위험한 코드를 배제하는 것이 가장 좋다.

    Promises는 데이터를 안전하게 처리할 수 있는 편리한 API가 부족합니다.


    위 부분에서, 우리는 기존의promise API가 어떻게 매력적인 위험성을 가지고 있는지 (두 개의 명명 함수를 사용하고, 함수마다 익명 파라미터를 사용하는지), 그리고 무의식중에 오류로부터 복구를 촉진하는지 이해했다.
    두 번째 문제는 promise API가 더 많은 도움을 주지 않았기 때문입니다.
    위의 마지막 예에서 우리.catch(logError)는 무의식중에 오류를 해결했다. 우리가 진정으로 원하는 것은 다른 것이다. 하나tap의 잘못된 부작용 함수이다.

    약속은 거절한 약속과 의외의 운행을 혼합한 이상


    API의 구조를 제외하고 약속에는 또 다른 주요 결함이 있다. 약속은 같은 '경로'에서 무의식적으로 본 컴퓨터가 운행할 때 이상하고 거절하려는 약속을 처리하는 것이다. 이 두 가지 의도는 확연히 다르다.
    const userPostHandler = rawUserData => {
      saveUser(userData)
        .then(() => response.send(204))
        .then({email} => postEmailToMailChimp(email))
        .catch(logError)
    };
    
    이 코드가 표현하고자 하는 내용은 매우 간단하다.(사용자가 메일chimp 목록에 저장되어 있으면 로그인하십시오.)
    그러나, 나는 실수로 함수 이름을 'Mail Chimp' 이 아니라 'Mail Chimp' 로 잘못 쳤다. 나는 지금 로그를 보기를 원하지 않을 수 없다. 개발할 때 나의 실행 오류를 일깨워 주는 것이 아니라, 이것은 내가 Mail Chimp 문제를 위해 설계한 것이지, 기본적인 프로그래밍 문제가 아니다.
    여기서 약속으로 근본적인 문제를 설명할 때 나는 행동을 약간 간소화했다. 모든 잘못(본기 잘못뿐만 아니라)을 약속하는 것은 약속을 거절하는 것과 같다.동의처리throwPromise.reject는 합리적인 것 같다.불합리한 것은 이'경로'를 사용하여 두 가지 서로 다른'유형'오류를 처리하는 데 차이가 없다.'전략적'오류(예를 들어 saveUser(user)가 사용자 정의 완전성 오류를 던지는 것)와 기본javascript가 실행될 때 오류(예를 들어saveUsr(사용자)가 입력 오류를 던지고 인용 오류를 던지는 것)이다.이것은 근본적으로 다른 두 종류의 부동산이지만, 그것들은 같은 약속을 거절하는 길에 묶여 있다.
    약속의 경우 데이터 '경로', 비네이티브 오류 '경로 (예: 사용자 정의, 업무 논리 오류) 와 네이티브 오류' 경로 '세 가지 경로가 있지만 API는 이러한 구분을 하지 않고 모든 오류와 거절에 대한 약속을 동일시한다.
    [2차 업데이트]
    [갱신] 이 글은 앞서'더 좋은'약속이 어떤 모습일 수 있는지에 대한 이론적 부분을 계속했다..."다음은 (이 문제들의 수많은 해결 방안 중 아주 나쁠 수도 있다) 무엇이 해결 방안일 수 있는지에 대한 사상 실험이다. 그것은 하나의 라이브러리가 되었다."만약 당신이 흥미가 있다면 이곳에서 읽을 수 있고,
    [Update]는 이 글에 대한 추문에 친절하게 답장을 했고 그의 견해를 제시했다. 나는 async/async 문법의 가치를 과소평가했다(까다로운 then/catch API를 추상화하고 우리를'정상'흐름으로 돌아가게 했다). 나머지 문제(즉 잘못 처리된 것)는 자바스크립트 자체의 문제(TC39가 계속 발전하고 있다).나는 이 생각을 확장시켰다.

    좋은 웹페이지 즐겨찾기