ES6-18 반복자 생성 - async 함수

카탈로그

  • async 함수
  • 의 의미
  • 기본 사용법
  • 문법
  • Promise 객체 반환
  • Promise 객체의 상태 변화
  • await 명령
  • 오류 처리
  • 사용 주의점
  • 다른 비동기 처리 방법과 비교
  • async 함수


    속뜻


    ES2017 표준은 async 함수를 도입하여 비동기 조작을 더욱 편리하게 하였다.
    async 함수는 무엇입니까?한마디로 Generator 함수의 문법 사탕이다.
    const gen = function* () {
      const f1 = yield console.log('1');
      const f2 = yield console.log('2');
    };
    
    //  `gen` `async` , 。
    
    const asyncReadFile = async function () {
      const f1 = await console.log('1');
      const f2 = await console.log('2');
    };
    

    비교해 보면 async 함수는 Generator 함수의 별표*)를 async로, yieldawait로 바꾸는 것이다.async 함수가 Generator 함수에 대한 개선은 다음과 같은 네 가지에 나타난다.
    (1) 내장된 실행기.async 함수 자체 실행기.즉 async 함수의 집행은 일반 함수와 똑같고 한 줄만 있으면 된다는 것이다.
    asyncReadFile();
    

    위의 코드가 asyncReadFile 함수를 호출한 후에 자동으로 실행되고 마지막 결과를 출력합니다.이것은 완전히 Generator 함수와 같지 않아서 next 방법을 호출해야만 진정으로 집행하고 최종 결과를 얻을 수 있다.
    (2) 더 좋은 의미.asyncawait는 별표와yield보다 의미가 더 명확해졌다.async는 함수에 비동기적인 조작이 있음을 나타내고await는 뒤에 뒤따르는 표현식이 결과를 기다려야 한다는 것을 나타낸다.
    (3) 반환 값은 Promise입니다.async 함수의 반환값은Promise 대상이며, 이것은Generator 함수의 반환값보다 Iterator 대상이 훨씬 편리하다.너는 then 방법으로 다음 조작을 지정할 수 있다.
    더 나아가 async 함수는 여러 개의 비동기적인 조작으로 볼 수 있고 하나의 Promise 대상으로 포장될 수 있다. await 명령은 내부then 명령의 문법 설탕이다.

    기본용법

    async 함수는 프로미스 대상을 되돌려줍니다. then 방법으로 리셋 함수를 추가할 수 있습니다.함수가 실행될 때 await를 만나면 먼저 되돌아와 비동기적인 조작이 완성될 때까지 기다린 다음에 함수 체내 뒤의 문장을 실행한다.
    다음은 몇 밀리초를 지정한 후에 값을 출력하는 예입니다.
    function timeout(ms) {
      return new Promise((resolve) => {
        setTimeout(resolve, ms);
      });
    }
    
    async function asyncPrint(value, ms) {
      await timeout(ms);
      console.log(value);
    }
    
    asyncPrint('hello world', 50);
    

    위 코드가 50밀리초를 지정한 후 출력hello world.async 함수는 Promise 대상을 되돌려주기 때문에 await 명령의 매개 변수로 사용할 수 있습니다.그래서 위의 예도 아래의 형식으로 쓸 수 있다.
    async function timeout(ms) {
      await new Promise((resolve) => {
        setTimeout(resolve, ms);
      });
    }
    
    async function asyncPrint(value, ms) {
      await timeout(ms);
      console.log(value);
    }
    
    asyncPrint('hello world', 50);
    

    async 함수는 여러 가지 사용 형식이 있다.
    //  
    async function foo() {}
    
    //  
    const foo = async function () {};
    
    //  
    let obj = { async foo() {} };
    obj.foo().then(...)
    
    // Class  
    class Storage {
      constructor() {
        this.cachePromise = caches.open('avatars');
      }
    
      async getAvatar(name) {
        const cache = await this.cachePromise;
        return cache.match(`/avatars/${name}.jpg`);
      }
    }
    
    const storage = new Storage();
    storage.getAvatar('jake').then();
    
    //  
    const foo = async () => {};
    

    구문

    async 함수의 문법 규칙은 전체적으로 비교적 간단하고 어려운 점은 오류 처리 메커니즘이다.

    Promise 객체로 돌아가기

    async 함수는 Promise 객체를 반환합니다.async 함수 내부return 문장이 되돌아오는 값은 then 방법 리셋 함수의 매개 변수가 된다.
    async function f() {
      return 'hello world';
    }
    
    f().then(v => console.log(v))
    // "hello world"
    

    위 코드에서 함수f 내부return 명령이 되돌아오는 값은 then 방법의 리셋 함수에 의해 수신됩니다.async 함수 내부에서 오류가 발생하면 되돌아오는 Promise 대상reject 상태가 됩니다.던진 오류 대상은 catch 방법 리셋 함수에 의해 수신됩니다.
    async function f() {
      throw new Error(' ');
    }
    
    f().then(
      v => console.log(v),
      e => console.log(e)
    )
    // Error:  
    

    Promise 객체의 상태 변경

    async 함수가 되돌아오는 Promise 대상은 내부 모든await 명령 뒤에 있는 Promise 대상이 실행될 때까지 기다려야 상태 변화가 발생합니다. return 문장이나 버퍼링 오류가 발생하지 않는 한.즉 async 함수 내부의 비동기 조작이 끝나야만 then 방법이 지정한 리셋 함수를 실행할 수 있다는 것이다.

    await 명령


    정상적으로 await 명령 뒤에는 Promise 객체가 있고 해당 객체의 결과가 반환됩니다.Promise 객체가 아니면 해당 값을 반환합니다.
    async function f() {
      //  
      // return 123;
      return await 123;
    }
    
    f().then(v => console.log(v))
    // 123
    

    위 코드에서 await 명령의 매개 변수는 수치123인데 이때는 return 123와 같다.
    또 다른 경우await 명령 뒤에 thenable 대상(즉 정의then 방법의 대상)이 있으면await은 이를 Promise 대상과 동일시한다.JavaScript는 항상 휴면 문법이 없지만 await 명령을 사용하면 프로그램이 지정한 시간을 멈출 수 있습니다.다음은 간략한sleep 실현을 제시했다.
    function sleep(interval) {
      return new Promise(resolve => {
        setTimeout(resolve, interval);
      })
    }
    
    //  
    async function one2FiveInAsync() {
      for(let i = 1; i <= 5; i++) {
        console.log(i);
        await sleep(1000);
      }
    }
    
    one2FiveInAsync();
    
    await 명령 뒤의 Promise 대상이 reject 상태가 되면 reject 방법의 리셋 함수에 의해 catch 인자가 수신됩니다.
    async function f() {
      await Promise.reject(' ');
    }
    
    f()
    .then(v => console.log(v))
    .catch(e => console.log(e))
    //  
    

    주의, 위 코드에서 await 문장 앞에는 return가 없지만 reject 방법의 매개 변수는 catch 방법의 리셋 함수로 전송되었다.여기에 await 앞에 return를 더하면 효과가 같다.await 문장 뒤에 있는 Promise 대상이 reject 상태가 되면 전체 async 함수가 실행을 중단합니다.
    async function f() {
      await Promise.reject(' ');
      await Promise.resolve('hello world'); //  
    }
    

    위 코드에서 두 번째await 문장은 실행되지 않습니다. 첫 번째await 문장 상태가 reject로 바뀌었기 때문입니다.
    때때로, 우리는 이전의 비동기 조작이 실패하더라도, 뒤의 비동기 조작을 중단하지 않기를 바란다.이때 첫 번째awaittry...catch 구조에 넣으면 이 비동기 조작이 성공하든 안 하든 두 번째await가 실행될 수 있다.
    async function f() {
      try {
        await Promise.reject(' ');
      } catch(e) {
      }
      return await Promise.resolve('hello world');
    }
    
    f()
    .then(v => console.log(v))
    // hello world
    

    또 다른 방법은 await 뒤에 있는 Promise 대상과 catch 방법을 따라 앞에 발생할 수 있는 오류를 처리하는 것이다.
    async function f() {
      await Promise.reject(' ')
        .catch(e => console.log(e));
      return await Promise.resolve('hello world');
    }
    
    f()
    .then(v => console.log(v))
    //  
    // hello world
    

    오류 처리

    await 뒤에 있는 비동기 동작이 잘못되면 async 함수가 되돌아오는 Promise 대상reject과 같다.
    async function f() {
      await new Promise(function (resolve, reject) {
        throw new Error(' ');
      });
    }
    
    f()
    .then(v => console.log(v))
    .catch(e => console.log(e))
    // Error: 
    

    위 코드에서 async 함수f가 실행되면 await 뒤에 있는 Promise 대상이 오류 대상을 던져서 catch 방법의 리셋 함수를 호출하는데 그 매개 변수는 바로 던진 오류 대상이다.
    오류를 방지하는 방법도 try...catch 코드 블록에 넣는다.
    async function f() {
      try {
        await new Promise(function (resolve, reject) {
          throw new Error(' ');
        });
      } catch(e) {
      }
      return await('hello world');
    }
    

    만약 여러 개await 명령이 있다면 try...catch 구조에 통일적으로 놓을 수 있다.
    async function main() {
      try {
        const val1 = await firstStep();
        const val2 = await secondStep(val1);
        const val3 = await thirdStep(val1, val2);
    
        console.log('Final: ', val3);
      }
      catch (err) {
        console.error(err);
      }
    }
    

    아래의 예는 try...catch 구조를 사용하여 여러 차례의 반복 시도를 실현했다.
    const superagent = require('superagent');
    const NUM_RETRIES = 3;
    
    async function test() {
      let i;
      for (i = 0; i < NUM_RETRIES; ++i) {
        try {
          await superagent.get('http://google.com/this-throws-an-error');
          break;
        } catch(err) {}
      }
      console.log(i); // 3
    }
    
    test();
    

    위 코드에서 await 조작이 성공하면 break 문장을 사용하여 순환을 종료합니다.실패하면 catch 문에 스냅되어 다음 순환에 들어갑니다.

    주의점 사용


    첫째, 앞에서 말했듯이 await 명령 뒤의 Promise 대상은 실행 결과rejected일 수 있으므로 await 명령을 try...catch 코드 블록에 넣는 것이 가장 좋다.
    async function myFunction() {
      try {
        await somethingThatReturnsAPromise();
      } catch (err) {
        console.log(err);
      }
    }
    
    //  
    
    async function myFunction() {
      await somethingThatReturnsAPromise()
      .catch(function (err) {
        console.log(err);
      });
    }
    

    둘째, 여러 개await의 명령 뒤에 있는 비동기 조작은 계발 관계가 존재하지 않으면 동시에 촉발하는 것이 좋다.
    let foo = await getFoo();
    let bar = await getBar();
    

    위 코드에서 getFoogetBar는 두 개의 독립된 비동기 조작(즉 서로 의존하지 않음)으로 계발 관계로 쓰였다.이렇게 하면 getFoo가 완성된 후에만 실행할 수 있기 때문에getBar 완전히 동시에 촉발할 수 있다.
    //  
    let [foo, bar] = await Promise.all([getFoo(), getBar()]);
    
    //  
    let fooPromise = getFoo();
    let barPromise = getBar();
    let foo = await fooPromise;
    let bar = await barPromise;
    

    위의 두 가지 쓰기 방법getFoogetBar은 모두 동시에 터치하여 프로그램의 실행 시간을 단축시킨다.
    세 번째, await 명령은 async 함수에만 사용할 수 있고 일반 함수에 사용하면 오류가 발생합니다.
    async function dbFuc(db) {
      let docs = [{}, {}, {}];
    
      //  
      docs.forEach(function (doc) {
        await db.post(doc);
      });
    }
    

    위의 코드는 await 일반 함수에 사용되었기 때문에 잘못 보고될 것이다.그러나 forEach 방법의 매개 변수를 async 함수로 바꾸는 것도 문제가 있다.
    function dbFuc(db) { //  async
      let docs = [{}, {}, {}];
    
      //  
      docs.forEach(async function (doc) {
        await db.post(doc);
      });
    }
    

    위 코드가 정상적으로 작동하지 않을 수도 있습니다. 왜냐하면 이때 세 개의 db.post 조작이 동시에 실행될 것입니다. 즉, 동시에 실행되는 것이지, 계속 실행되는 것이 아닙니다.정확한 쓰기 방법은 for 순환을 채택하는 것이다.
    async function dbFuc(db) {
      let docs = [{}, {}, {}];
    
      for (let doc of docs) {
        await db.post(doc);
      }
    }
    

    여러 개의 요청이 동시에 실행되기를 원한다면 Promise.all 방법을 사용할 수 있습니다.세 가지 요청이 모두 resolved 될 때, 아래의 두 가지 쓰기 효과는 같다.
    async function dbFuc(db) {
      let docs = [{}, {}, {}];
      let promises = docs.map((doc) => db.post(doc));
    
      let results = await Promise.all(promises);
      console.log(results);
    }
    
    //  
    
    async function dbFuc(db) {
      let docs = [{}, {}, {}];
      let promises = docs.map((doc) => db.post(doc));
    
      let results = [];
      for (let promise of promises) {
        results.push(await promise);
      }
      console.log(results);
    }
    

    다른 비동기 처리 방법과 비교


    우리는 하나의 예를 통해 async 함수와Promise,Generator 함수의 비교를 보았다.
    DOM 요소 위에 이전 애니메이션이 끝나야 다음 애니메이션이 시작된다고 가정합니다.애니메이션 중 하나가 잘못되면 더 이상 아래로 내려가지 않고 마지막으로 성공적으로 실행된 애니메이션의 되돌아오는 값을 되돌려줍니다.
    먼저 Promise 쓰기
    function chainAnimationsPromise(elem, animations) {
    
      //  ret 
      let ret = null;
    
      //  Promise
      let p = Promise.resolve();
    
      //  then , 
      for(let anim of animations) {
        p = p.then(function(val) {
          ret = val;
          return anim(elem);
        });
      }
    
      //  Promise
      return p.catch(function(e) {
        /*  ,  */
      }).then(function() {
        return ret;
      });
    
    }
    

    프로미스의 쓰기는 리셋 함수의 쓰기보다 크게 개선되었지만 한눈에 보면 코드가 완전히 프로미스의 API(then,catch 등)로 조작 자체의 의미를 오히려 알아내기 어렵다.
    그 다음은 async 함수의 쓰기 방법입니다.
    async function chainAnimationsAsync(elem, animations) {
      let ret = null;
      try {
        for(let anim of animations) {
          ret = await anim(elem);
        }
      } catch(e) {
        /*  ,  */
      }
      return ret;
    }
    

    Async 함수의 실현이 가장 간결하고 의미에 가장 부합되며 의미와 상관없는 코드가 거의 없다는 것을 알 수 있다.Generator 쓰기 방법의 자동 실행기를 언어 차원에서 제공하여 사용자에게 노출되지 않기 때문에 코드량이 가장 적습니다.Generator Write를 사용하는 경우 자동 실행기는 사용자가 직접 제공해야 합니다.

    좋은 웹페이지 즐겨찾기