처음부터 JavaScript 약속 만들기

이 게시물은 최초로 Human Who Codes blogSeptember 29, 2020에 등장했다.
이 시리즈의 first post에서 나는 Promise의 구조 함수를 Pledge의 구조 함수로 다시 만들어서 그것이 어떻게 작동하는지 설명했다.나는 그 문장에서 구조 함수는 비동기성이 없고 모든 비동기적인 조작은 뒤에서 발생한다고 지적했다.본고에서 나는 어떻게 한 약속을 다른 약속으로 전환하는지 소개할 것이다. 이것은 비동기적인 조작을 촉발할 것이다.
이 시리즈는 제 약속 라이브러리 Pledge을 바탕으로 합니다.GitHub에서 모든 소스 코드를 보고 다운로드할 수 있습니다.

작업 및 미세 작업
실현을 시작하기 전에promises에서 비동기적인 조작 메커니즘을 토론하는 것이 도움이 된다.비동기 약속 작업은 ECMA-262에서 작업으로 정의됩니다[1].

A Job is an abstract closure with no parameters that initiates an ECMAScript computation when no other ECMAScript computation is currently in progress.


더 간단한 언어로 말하자면, 이 규범은 작업은 다른 함수가 실행되지 않을 때 실행되는 함수라고 한다.하지만 흥미로운 것은 이 과정의 디테일이다.다음은 사양에 명시된 [1]입니다.
  • At some future point in time, when there is no running execution context and the execution context stack is empty, the implementation must:
    1. Push an execution context onto the execution context stack.
    2. Perform any implementation-defined preparation steps.
    3. Call the abstract closure.
    4. Perform any implementation-defined cleanup steps.
    5. Pop the previously-pushed execution context from the execution context stack.> > * Only one Job may be actively undergoing evaluation at any point in time.
  • Once evaluation of a Job starts, it must run to completion before evaluation of any other Job starts.
  • The abstract closure must return a normal completion, implementing its own handling of errors.

하나의 예로 이 과정을 생각하는 것이 가장 쉽다.웹 페이지의 단추에 onclick 이벤트 처리 프로그램을 설정했다고 가정하십시오.이 단추를 누르면 이벤트 처리 프로그램을 실행하기 위해 새로운 실행 상하문을 실행 상하문 창고로 전송합니다.이벤트 처리 프로그램이 실행되면 실행 상하문이 창고에서 튀어나오고 창고가 비어 있습니다.작업을 수행할 시간입니다. 그런 다음 더 많은 JavaScript가 실행될 때까지 기다린 이벤트 사이클을 반환합니다.
JavaScript 엔진에서 버튼의 이벤트 처리기는 작업으로 간주되고 작업은 마이크로 작업으로 간주됩니다.작업 중에 대기열에 추가된 모든 미세 작업은 작업이 완료되면 바로 대기열에 추가된 순서에 따라 수행됩니다.다행히도 너와 나, 브라우저, 노드.js와 Deno는 queueMicrotask() 함수를 가지고 있으며, 마이크로 작업의 줄을 서는 데 사용됩니다.queueMicrotask() 함수는 HTML 규범[2]에서 정의한 것으로 매개 변수, 즉 마이크로 작업으로 호출되는 함수를 받아들인다.예를 들면 다음과 같습니다.
queueMicrotask(() => {
    console.log("Hi");
});
이 예제에서는 현재 작업이 완료되면 콘솔에 "Hi"을 출력합니다.마이크로 작업은 항상 setTimeout() 또는 setInterval()에서 만든 타이머를 사용하기 전에 실행된다는 것을 기억하십시오.타이머는 마이크로 작업이 아닌 작업을 사용하기 때문에 작업을 수행하기 전에 이벤트 순환으로 되돌아옵니다.
Credit의 코드를 규범처럼 보이기 위해 hostEnqueuePledgeJob() 함수를 정의했습니다. queueMicrotask()을 간단하게 호출할 수 있습니다.
export function hostEnqueuePledgeJob(job) {
    queueMicrotask(job);
}
NewPromiseResolveThenJob 업무
이전 게시물에서 다른 약속이 resolve에 전달되었을 때 나는 약속을 어떻게 해결하는지 보여주지 않았다.비thenable값과 반대로 다른 약속을 통해 resolve을 호출하는 것은 두 번째 약속이 해결되기 전에 첫 번째 약속이 해결되지 않는다는 것을 의미합니다. 이를 위해 NewPromiseResolveThenableJob()이 필요합니다.NewPromiseResolveThenableJob()은 세 가지 매개 변수를 받아들인다. 해석된 약속, resolve에 전달된 thenable와 호출된 then() 함수이다.그리고 이 작업은 발생할 수 있는 잠재적인 오류를 포획하는 동시에 이 표의 resolve 방법을 해석하기 위해 rejectthen() 함수를 프로미스에 추가한다.NewPromiseResolveThenableJob()을 실현하기 위해서, 나는 되돌아오는 함수를 가진 구조 함수 클래스를 사용하기로 결정했다.이것은 좀 이상하게 보이지만, 코드는 new 조작부호를 사용하여 새로운 작업을 만드는 것처럼 보일 것이다. new으로 시작하는 함수를 만드는 것이 아니라. (나는 이상하다고 생각한다.)다음은 나의 실현이다.
export class PledgeResolveThenableJob {
    constructor(pledgeToResolve, thenable, then) {
        return () => {
            const { resolve, reject } = createResolvingFunctions(pledgeToResolve);

            try {
                // same as thenable.then(resolve, reject)
                then.apply(thenable, [resolve, reject]);
            } catch (thenError) {
                // same as reject(thenError)
                reject.apply(undefined, [thenError]);
            }
        };
    }
}
createResolvingFunctions()의 사용을 알 수 있습니다. Pledge의 구조 함수에서도 사용됩니다.이 호출은 새로운 resolvereject 함수를 만들었습니다. 구조 함수 내부에서 사용하는 원시 함수와 다릅니다.그리고 이 함수들을 실현 및 거부 처리 프로그램으로 thenable에 추가해 보십시오.코드가 좀 이상해 보입니다. 가능한 한 규범에 가까워 보이려고 했지만, 실제로는 thenable.then(resolve, reject)에 불과합니다.이 코드는 포획하여 try-catch 함수에 전달해야 하는 오류가 발생하지 않도록 reject에 포장되어 있다.다시 한 번, 내가 규범화된 정신을 포착하려고 시도했을 때, 코드는 좀 복잡해 보였지만, 결국 reject(thenError)에 불과했다.
이제 resolvecreateResolvingFunctions() 함수 정의를 되돌려 완성하고 PledgeResolveThenableJob을 터치하여 마지막 단계로 삼을 수 있습니다.
export function createResolvingFunctions(pledge) {

    const alreadyResolved = { value: false };

    const resolve = resolution => {

        if (alreadyResolved.value) {
            return;
        }

        alreadyResolved.value = true;

        // can't resolve to the same pledge
        if (Object.is(resolution, pledge)) {
            const selfResolutionError = new TypeError("Cannot resolve to self.");
            return rejectPledge(pledge, selfResolutionError);
        }

        // non-objects fulfill immediately
        if (!isObject(resolution)) {
            return fulfillPledge(pledge, resolution);
        }

        let thenAction;

        try {
            thenAction = resolution.then;
        } catch (thenError) {
            return rejectPledge(pledge, thenError);
        }

        // if the thenAction isn't callable then fulfill the pledge
        if (!isCallable(thenAction)) {
            return fulfillPledge(pledge, resolution);
        }

        /*
         * If `thenAction` is callable, then we need to wait for the thenable
         * to resolve before we can resolve this pledge.
         */
        const job = new PledgeResolveThenableJob(pledge, resolution, thenAction);
        hostEnqueuePledgeJob(job);
    };

    // attach the record of resolution and the original pledge
    resolve.alreadyResolved = alreadyResolved;
    resolve.pledge = pledge;

    // reject function omitted for ease of reading

    return {
        resolve,
        reject
    };
}
resolution이 thenable이면 PledgeResolveThenableJob을 만들고 줄을 서세요.이 점은 매우 중요하다. 모든 테이블이 resolve에 전달되기 때문에, 이것은 동기화 해석이 아니라는 것을 의미하며, 최소한의 미세한 작업이 완성되기를 기다려야 한다.

마무리
이 글에서 가장 중요한 개념은 작업이 어떻게 작동하는지, 그리고 작업이 자바스크립트가 실행될 때의 미세한 작업과 어떻게 연결되는지 하는 것이다.일은 약속 기능의 핵심 부분이다. 이 글에서 한 일을 다른 약속으로 바꾸는 방법을 배웠다.이러한 배경이 있으면 then(), catch()finally()을 실현할 수 있습니다. 모두 같은 유형의 작업에 의존하여 그들의 처리 프로그램을 촉발할 수 있습니다.이것은 이 시리즈의 다음 문장에서 소개할 것이다.
참고: 모든 코드는 GitHub의 Pledge에서 찾을 수 있습니다.나는 네가 약속을 더 잘 이해하기 위해 그것을 다운로드하고 시험해 볼 수 있기를 바란다.

도구책
  • Jobs and Host Operations to Enqueue Jobs
  • Microtask queueing
  • 좋은 웹페이지 즐겨찾기