Thenable: JavaScript 객체를 대기 친화적으로 만드는 방법과 유용한 이유

그러면 가능이란 무엇입니까?



이 짧은 게시물은 모든 JavaScript 클래스 또는 객체 리터럴에 .then(onFulfilled, onRejected) 메서드를 추가하여 await 와 잘 작동하도록 할 수 있음을 상기시키기 위한 것입니다. 개체가 비동기 작업을 수행할 때 유용합니다.

많은 C# 개발자는 "사용자 지정 대기자"의 개념에 익숙합니다(Stephen Toub의 "Await anything" 참조). 흥미롭게도 JavaScript에서는 문자 그대로 모든 것이 있는 그대로 기다릴 수 있지만(예: try (await true) === true ) 언어는 C# awaitables: thenable objects 또는 Thenables와 유사한 기능도 제공합니다.

Thenable은 약속이 아니지만 await 연산자의 오른쪽에서 의미 있게 사용할 수 있으며 , Promise.race() 등과 같은 많은 표준 JavaScript API에서 허용됩니다. 예를 들어 thenable를 a로 래핑할 수 있습니다. 다음과 같이 선의의 약속을 합니다.

const promise = Promise.resolve(thenable);


이면에서 작동하는 방식에 대해 더 자세히 알고 싶다면 V8 블로그를 참조하세요. "Faster async functions and promises" .

샘플 사용 사례



먼저 jQuery Deffered .NET Deferred 에서 영감을 받은 TaskCompletionSource 객체를 생성해 보겠습니다.

function createDeferred() {
  let resolve, reject;

  const promise = new Promise((...args) => 
    [resolve, reject] = args);

  return Object.freeze({
    resolve, 
    reject,
    then: promise.then.bind(promise)
  });
}

const deferred = createDeferred();
// resolve the deferred in 2s 
setTimeout(deferred.resolve, 2000);
await deferred;


완전성을 위해 the same in TypeScript .

이제 약간 인위적이지만 thenable가 적절한 리소스 정리(이 경우 타이머)에 어떻게 유용할 수 있는지 보여주는 좀 더 예시적인 예가 되기를 바랍니다.

function createStoppableTimer(ms) {
  let cleanup = null;
  const promise = new Promise(resolve => {
    const id = setTimeout(resolve, ms);
    cleanup = () => {
      cleanup = null;
      clearTimeout(id);
      resolve(false);
    }
  });
  return Object.freeze({
    stop: () => cleanup?.(),
    then: promise.then.bind(promise) 
  });
}

const timeout1 = createStoppableTimeout(1000);
const timeout2 = createStoppableTimeout(2000);
try {
  await Promise.race([timeout1, timeout2]);
}
finally {
  timeout1.stop();
  timeout2.stop();
}


확실히 우리는 속성으로 promise를 노출할 수 있었습니다.

await Promise.race([timeout1.promise, timeout2.promise]);


그것은 효과가 있지만 나는 팬이 아닙니다. asyncWorkflow가 비동기 작업을 나타내는 곳은 속성 중 하나가 아니라 await asyncWorkflow 자체가 가능해야 한다고 생각합니다. 그것이 asyncWorkflow.then(onFulfilled, onRejected) 구현이 도움이 되는 곳입니다.

다음은 이벤트 핸들러 구독을 적절하게 정리하면서 임의의 EventTarget 이벤트를 비동기식으로 기다리는 방법에 대한 또 하나의 예입니다. 다음 2초 이내에 팝업 창이 닫히기를 기다리고 있습니다.

const eventObserver = observeEvent(
  popup, "close", event => event.type);

const timeout = createStoppableTimeout(2000);

try {
   await Promise.race([eventObserver, timeout]);
}
catch (error) {
  console.error(error);
}
finally {
  timeout.stop();
  eventObserver.close();
}


This is what the observeEvent implementation may look like (note how it returns an object with then and close methods):
function observeEvent(eventSource, eventName, onevent) { 
  let cleanup = null;

  const promise = observe();
  return Object.freeze({
    close: () => cleanup?.(),
    then: promise.then.bind(promise)
  });

  // an async helper to wait for the event
  async function observe() {
    const eventPromise = new Promise((resolve, reject) => {
      const handler = (...args) => {
        try {
          resolve(onevent?.(...args));
        }
        catch (error) {
          reject(error);
        }
        finally {
          cleanup?.();
        }
      };

      cleanup = () => {
        cleanup = null;
        eventSource.removeEventListener(handler);
      } 

      eventSource.addEventListener(
        eventName, handler, { once: true });
    });

    try {
      return await eventPromise;      
    } 
    finally {
      cleanup?.();    
    }
  }
}

I use this pattern a lot, as it helps with properly structured error handling and scoped resources management. The errors are propagated from inside the event handler (if any) by rejecting the internal promise, so await eventObserver will rethrow them.

As the current TC39 "ECMAScript Explicit Resource Management" 제안이 진행 중이며 곧 다음과 같은 작업을 수행할 수 있습니다.

const eventObserver = observeEvent(
  popup, "close", event => "closed!");

const timeout = createStoppableTimeout(2000);

try using (eventObserver, timeout) {
   await Promise.race([eventObserver, timeout]);
}


정리 메서드를 명시적으로 호출할 필요가 없습니다.

나는 미래의 블로그 게시물에서 Ron Buckton의 또 다른 중요한 TC39 제안ECMAScript Cancellation을 오늘 대안으로 사용할 수 있는 것을 포함하여 더 자세히 다루기를 희망합니다.

읽어 주셔서 감사합니다! 아래 또는 에 댓글을 남겨주세요.

좋은 웹페이지 즐겨찾기