JavaScript의 비동기식 취소 가능 함수

21342 단어 promiseasyncjavascript
(이 글은 async 함수에 대한 중복 호출을 처리하기 위해 생성기를 사용하는 방법을 설명합니다. Check out this gist for the final approach 또는 더 많은 것을 알기 위해 계속 읽으십시오!🎓)
자바스크립트는 무서운 비동기 호출로 이루어진 굴곡의 미로로, 모든 호출은 같다.우리는 모두 이런 코드를 쓴 적이 있지만, 이 문장에서 나는 asyncawait을 토론할 것이다.이 키워드는 widely supported으로 코드를 더 읽을 수 있는 곳으로 옮길 수 있습니다.📖👀
가장 중요한 것은, 나는 관건적인 함정을 소개할 것이다. 어떻게 비동기적인 방법이 여러 번 실행되고, 이렇게 하면 다른 업무에 영향을 주지 않을 것이다.🏑💥
우리 이 예부터 시작합시다.이 함수는 몇 가지 내용을 가져와 화면에 표시하고 몇 초를 기다린 다음 주의하십시오.
function fetchAndFlash(page) {
  const jsonPromise = fetch('/api/info?p=' + page)
      .then((response) => response.json());
  jsonPromise.then((json) => {
    infoNode.innerHTML = json.html;

    setTimeout(() => {
      flashForAttention(infoNode);
    }, 5000);
  });
}
이제 asyncawait으로 다시 쓸 수 있습니다. 리셋이 없습니다.
async function fetchAndFlash(page) {
  const response = await fetch('/api/info?p=' + page);
  const json = await response.json();
  infoNode.innerHTML = json.html;

  // a bit awkward, but you can make this a helper method
  await new Promise((resolve) => setTimeout(resolve, 5000));

  flashForAttention(infoNode);
}
이게 낫지 않아요?이것은 이리저리 뛰어다니며 위에서 아래로 내려가는 절차를 쉽게 볼 수 있다. 자원을 얻고 JSON으로 변환하여 페이지에 쓰고 5초를 기다린 후에 다른 방법을 호출한다.🔜

이건 함정이야!


그러나 이곳에는 독자들을 곤혹스럽게 할 수도 있다.이것은'일회성'으로 실행되는 일반적인 함수가 아니다. await을 호출할 때마다 우리는 기본적으로 브라우저의 이벤트 순환을 따른다. 이렇게 하면 계속 작업을 할 수 있다.⚡🤖fetchAndFlash()을 사용하는 코드를 읽고 있다고 가정하십시오.만약 당신이 이 글의 제목을 읽지 않았다면, 만약 당신이 이 코드를 실행한다면, 당신은 무슨 일이 발생하기를 기대합니까?
fetchAndFlash('page1');
fetchAndFlash('page2');
너는 하나하나 발생하기를 기대할 수도 있고, 혹은 하나하나가 다른 것을 취소할 수도 있다.그러나 사실은 그렇지 않습니다. 둘 다 병행 실행될 것입니다. (대기할 때 JavaScript가 막을 수 없기 때문에) 임의의 순서로 완성되고 HTML이 최종적으로 페이지에 나타날지 알 수 없습니다.⚠️

분명히 이런 방법은 리셋된 버전을 바탕으로 하는 것도 완전히 같은 문제가 있지만, 매우 혐오스러운 방식으로 더욱 뚜렷하다.코드를 asyncawait으로 현대화할 때 우리는 그것을 더욱 모호하게 한다.😕
우리들은 몇 가지 다른 방법을 소개하여 이 문제를 해결합시다.안전벨트 매!🎢

메서드 #1: 체인

async 방법을 호출하는 방식과 원인에 따라 하나씩 링크할 수 있습니다.클릭 이벤트를 처리하는 경우:
let p = Promise.resolve(true);
loadButton.onclick = () => {
  const pageToLoad = pageToLoadInput.value;
  // wait for previous task to finish before doing more work
  p = p.then(() => fetchAndFlash(pageToLoad));
};
클릭할 때마다 체인에 다른 작업이 추가됩니다.우리는 또한 보조 함수로 이 점을 요약할 수 있다.
// makes any function a chainable function
function makeChainable(fn) {
  let p = Promise.resolve(true);
  return (...args) => {
    p = p.then(() => fn(...args));
    return p;
  };
}
const fetchAndFlashChain = makeChainable(fetchAndFlash);
현재, 당신은 fetchAndFlashChain()으로 전화를 걸기만 하면 됩니다. 그것은 다른 fetchAndFlashChain()으로 전화를 걸면 순서대로 발생합니다.🔗
그러나 이것은 본문의 건의가 아닙니다. 만약 우리가 이전의 조작을 취소하고 싶다면 어떻게 합니까?사용자가 방금 다른 불러오기 단추를 눌렀기 때문에 이전의 일에 관심이 없을 수도 있습니다.🙅

방법 #2: 장애물 검사


우리의 현대화된 fetchAndFlash()에서 우리는 await 키워드를 세 번 사용했는데 실제로는 두 가지 다른 원인만 있다.
  • 네트워크 캡처
  • 5초 대기 후 깜박임
  • 이 두 시가 지난 후에 우리는 멈추고 물었다. "헤이, 우리가 여전히 가장 활발한 임무입니까? 사용자가 최근에 하고 싶은 일입니까?"🤔💭
    우리는 a nonce으로 각각의 조작을 표시함으로써 이 점을 실현할 수 있다.이것은 로컬 및 글로벌에 이 객체를 저장하고 다른 작업이 로컬 버전에서 시작되었기 때문에 글로벌 버전이 서로 다른지 확인하는 유일한 객체를 만드는 것을 의미합니다.
    다음은 저희가 업데이트한 fetchAndFlash() 방법입니다.
    let globalFetchAndFlashNonce;
    async function fetchAndFlash(page) {
      const localNonce = globalFetchAndFlashNonce = new Object();
    
      const response = await fetch('/api/info?p=' + page);
      const json = await response.json();
      // IMMEDIATELY check
      if (localNonce !== globalFetchAndFlashNonce) { return; }
    
      infoNode.innerHTML = json.html;
    
      await new Promise((resolve) => setTimeout(resolve, 5000));
      // IMMEDIATELY check
      if (localNonce !== globalFetchAndFlashNonce) { return; }
    
      flashForAttention(infoNode);
    }
    
    이것은 매우 좋지만, 좀 지나치다.요약하기도 쉽지 않으니 중요한 곳에 검사를 추가하는 것을 기억해야 한다!
    비록 generators을 사용하여 우리를 개괄하는 방법이 있다.

    배경: 발전기


    우리의 예에서, await은 기다리는 일이 끝날 때까지 실행을 늦추고, 네트워크 요청이나 시간 초과만 기다린다. 생성기 함수는 기본적으로 상반된 일을 하고, 그 위치를 되돌려 사용한다.
    곤혹스러웠어특히
    function* myGenerator() {
      const finalOut = 300;
      yield 1;
      yield 20;
      yield finalOut;
    }
    for (const x of myGenerator()) {
      console.info(x);
    }
    // or, slightly longer (but exactly the same output)
    const iterator = myGenerator();
    for (;;) {
      const next = iterator.next();
      if (next.done) {
        break;
      }
      console.info(next.value);
    }
    
    이 두 버전의 프로그램은 모두 1, 20, 300을 인쇄할 것이다.흥미로운 것은 내가 for 순환에서 좋아하는 일을 할 수 있다는 것이다. break 초기, myGenerator의 모든 상태가 변하지 않고, 내가 성명한 모든 변수, 그리고 내가 어디에 있는지를 포함한다.
    여기는 보이지 않지만 생성기를 호출하는 코드 (특히 교체기의 .next() 함수) 도 변수를 사용하여 복구할 수 있습니다.우리는 기대하고 있다.
    만약 우리가 어떤 작업을 멈추기로 결정한다면, 우리는 이 부분들을 결합시켜서 더 이상 일을 하지 않거나, 출력을 사용하여 실행을 회복할 수 있다.응, 우리 문제에 잘 어울리는 것 같아!✅

    솔루션🎉


    마지막으로 fetchAndFlash()을 다시 쓰겠습니다.실제로 우리는 함수 유형 자체를 변경하고 awaityield을 교환하기만 하면 된다. 호출자는 우리를 기다릴 수 있고 다음 단계는 어떻게 조작하는지 볼 수 있다.
    function* fetchAndFlash(page) {
      const response = yield fetch('/api/info?p=' + page);
      const json = yield response.json();
    
      infoNode.innerHTML = json.html;
    
      yield new Promise((resolve) => setTimeout(resolve, 5000));
    
      flashForAttention(infoNode);
    }
    
    이 코드는 지금 정말 의미가 없습니다. 만약 우리가 그것을 사용하려고 시도한다면, 그것은 붕괴될 것입니다.모든 Promise을 생성하는 요점은 현재 이 생성기를 호출하는 함수들이 nonce 검사를 포함하여 await을 실행할 수 있다는 것이다.현재 yield을 사용하면 기다릴 때 이 줄을 삽입할 필요가 없습니다.
    가장 중요한 것은 이 방법이 현재 async 함수가 아니라 생성기이기 때문에 await 키워드는 사실상 오류이다.이것은 당신이 정확한 코드를 작성하는 것을 확보하는 가장 좋은 방법입니다!🚨
    우리는 어떤 기능을 필요로 합니까?그래, 이것이 이 글의 진정한 매력이다.
    function makeSingle(generator) {
      let globalNonce;
      return async function(...args) {
        const localNonce = globalNonce = new Object();
    
        const iter = generator(...args);
        let resumeValue;
        for (;;) {
          const n = iter.next(resumeValue);
          if (n.done) {
            return n.value;  // final return value of passed generator
          }
    
          // whatever the generator yielded, _now_ run await on it
          resumeValue = await n.value;
          if (localNonce !== globalNonce) {
            return;  // a new call was made
          }
          // next loop, we give resumeValue back to the generator
        }
      };
    }
    
    신기하지만 그것도 의미가 있었으면 좋겠어요.우리는 전달된 생성기를 호출하여 교체기를 얻었다.그리고 우리는 생성된 모든 값에 await을 실행하고 생성기가 완성될 때까지 네트워크 응답처럼 생성된 값을 복구합니다.중요한 것은 우리가 매번 비동기적인 조작을 한 후에 전체 국면과 일부 nonce를 검사할 수 있게 하는 것이다.
    분기: 새 호출이 진행되면 하나의 호출이 취소되었는지 확인하는 것이 유용하기 때문에 특수한 값을 되돌려줍니다.sample gist에서 나는 Symbol, 그것과 비교할 수 있는 유일한 대상으로 돌아왔다.
    마지막으로, 우리는 실제로 makeSingle을 사용하고 우리의 생성기를 다른 사람이 사용할 수 있도록 포장했기 때문에 현재의 작업 원리는 일반적인 비동기적인 방법과 유사하다.
    // replaces fetchAndFlash so all callers use it as an async method
    fetchAndFlash = makeSingle(fetchAndFlash);
    
    // ... later, call it
    loadButton.onclick = () => {
      const pageToLoad = pageToLoadInput.value;
      fetchAndFlash(pageToLoad);  // will cancel previous work
    };
    
    대단히 좋다현재, 당신은 어느 곳에서든 fetchAndFlash()으로 전화를 걸 수 있으며, 이전의 모든 통화는 가능한 한 빨리 취소될 것이라는 것을 알고 있습니다.

    방백: 추출 중단 가능


    열성적인 사람들은 내가 위에서 소개한 것은 단지 한 가지 방법을 취소했을 뿐, 어떤 비행 중의 일도 중단하지 않았다는 것을 알아차릴 수 있을 것이다.내가 말한 것은 fetch이다. 그것은 지원되는 방식 to abort the network request을 가지고 있다.만약 비동기적인 기능이 매우 큰 파일을 다운로드한다면, 이것은 사용자의 대역폭을 절약할 수 있을 것이다. 그러나 우리가 하는 것은 다운로드를 막지 않을 것이다. 우리는 파일이 귀중한 바이트를 다 쓴 후에 다운로드를 취소할 뿐이다.

    도은


    만약 당신이 여기까지 읽었다면, 자바스크립트의 작업 방식에 대해 더 많은 생각을 할 수 있기를 바랍니다.
    비동기적인 작업을 해야 할 때 JS는 막을 수 없습니다. 방법에 대한 여러 번의 호출이 발생할 수 있습니다. 이 링크를 처리하거나 본고의 전체 주제에서 말한 바와 같이 이전의 호출을 취소할 수 있습니다.
    읽어주셔서 감사합니다!👋

    좋은 웹페이지 즐겨찾기