Promise, async 및 await

12824 단어
지옥으로 돌아가다
readFile("example.txt", function(err, contents) {
    if (err) {
        throw err; 
    }
    writeFile("example.txt", function(err) {
        if (err) {
            throw err; 
        }
        console.log("File was written!");
    });
});
method1(function(err, result) {
    if (err) {
        throw err;
    }
    method2(function(err, result) {
        if (err) {
            throw err;
        }
        method3(function(err, result) {
            if (err) {
                throw err;
            }
            method4(function(err, result) {
                if (err) {
                    throw err;
                }
                method5(result);
            });
        });
    });
});

지옥에 여러 가지 방법을 끼워 넣으면 복잡한 코드를 만들 수 있고 이해하기 어렵고 디버깅하기 어렵다.더 복잡한 기능을 실현하려고 할 때, 리셋 함수에도 문제가 존재할 수 있다.
Promise 정의
Promise는 비동기 프로그래밍의 해결 방안으로 전통적인 해결 방안인 리셋 함수와 사건보다 더욱 합리적이고 강력하다.이것은 지역 사회가 최초로 제기하고 실현한 것으로 ES6는 이를 언어 표준에 쓰고 용법을 통일시켰으며 원생은 Promise 대상을 제공했다.
이른바 Promise란 간단하게 말하면 하나의 용기로 미래에 끝날 사건(일반적으로 비동기적인 조작)의 결과를 저장하고 있다.문법적으로 프로미스는 비동기적인 조작에 대한 정보를 얻을 수 있는 대상이다.Promise는 다양한 비동기식 작업을 동일한 방법으로 처리할 수 있는 통일된 API를 제공합니다.
Promise 특징
  • 대상의 상태는 외부의 영향을 받지 않는다.Promise 대상은 비동기적인 동작을 대표합니다. 세 가지 상태가 있습니다.pending(진행 중),fulfilled(성공),rejected(실패).비동기적인 조작의 결과만 현재 어떤 상태인지 결정할 수 있으며, 다른 조작도 이 상태를 바꿀 수 없습니다
  • 일단 상태가 바뀌면 다시 변하지 않고 언제든지 이 결과를 얻을 수 있다.Promise 대상의 상태 변화는 단지 두 가지 가능성만 있습니다. pending에서fulfilled로,pending에서rejected로 바뀔 수 있습니다.이 두 가지 상황이 발생하면 상태가 응고되어 다시는 변하지 않고 이 결과를 유지할 것이다.변경이 발생하면 Promise 대상에 리셋 함수를 추가하면 바로 이 결과를 얻을 수 있습니다.이것은 사건(Event)과 완전히 다르다. 사건의 특징은 당신이 그것을 놓치면 감청하면 결과를 얻지 못한다는 것이다

  • Promise 대상이 있으면 비동기 조작을 동기화 조작의 절차로 표현할 수 있고 층층이 끼워 넣은 리셋 함수를 피할 수 있다.그 밖에 Promise 대상은 통일된 인터페이스를 제공하여 비동기적인 조작을 제어하는 것을 더욱 쉽게 한다.
    기본용법
    Promise 객체는 Promise 인스턴스를 생성하는 데 사용되는 구조 함수입니다.
    const promise = new Promise(function(resolve, reject) {
      // ... some code
    
      if (/*   */){
        resolve(value);
      } else {
        reject(error);
      }
    });
    

    Promise 구조 함수는 하나의 함수를 매개 변수로 받아들인다. 이 함수의 두 매개 변수는 각각resolve와reject이다.그것들은 두 개의 함수로 자바스크립트 엔진에서 제공하며 스스로 배치할 필요가 없다.
  • resolve 함수의 역할은 Promise 대상의 상태를 완성하지 않은 상태에서 성공(즉pending에서resolved)으로 바꾸고, 비동기 조작이 성공할 때 호출하고, 비동기 조작의 결과를 매개 변수로 전달하는 것이다
  • reject 함수의 역할은 Promise 대상의 상태를 완성하지 못한 상태에서 실패(즉pending에서rejected)로 바꾸고, 비동기 작업이 실패했을 때 호출하고, 비동기 작업이 보고한 오류를 매개 변수로 전달하는 것이다

  • Promise 실례가 생성된 후, then 방법으로 각각resolved 상태와rejected 상태의 리셋 함수를 지정할 수 있습니다.
    promise.then(function(value) {
      // success
    }, function(error) {
      // failure
    });
    

    then 방법은 두 개의 리셋 함수를 매개 변수로 받아들일 수 있다.첫 번째 리셋 함수는 Promise 대상의 상태가resolved로 변할 때 호출되고, 두 번째 리셋 함수는 Promise 대상의 상태가rejected로 변할 때 호출됩니다.그 중에서 두 번째 함수는 선택할 수 있으며 반드시 제공해야 하는 것은 아니다.이 두 함수는 Promise 객체에서 전달되는 값을 매개 변수로 사용합니다.
    function timeout(ms) {
      return new Promise((resolve, reject) => {
        setTimeout(resolve('done'), ms);
      });
    }
    
    timeout(100).then((value) => {
      console.log(value);
    });
    

    resolve 함수와reject 함수를 호출할 때 파라미터가 있으면 파라미터가 리셋 함수에 전달됩니다.reject 함수의 매개 변수는 일반적으로 Error 대상의 실례로 던지는 오류를 나타낸다.resolve 함수의 매개 변수는 정상적인 값 이외에 다른 Promise 실례일 수도 있습니다.
    const p1 = new Promise(function (resolve, reject) {
      setTimeout(() => reject(new Error('fail')), 3000)
    })
    
    const p2 = new Promise(function (resolve, reject) {
      setTimeout(() => resolve(p1), 1000)
    })
    
    p2.then(result => console.log(result)).catch(error => console.log(error))
    // Error: fail
    

    p1은 Promise로 3초 후에 rejected로 바뀝니다.p2의 상태는 1초 후에 바뀌고,resolve 방법은 p1을 되돌려줍니다.p2가 다른 Promise로 되돌아왔기 때문에 p2 자신의 상태가 무효입니다. p1의 상태가 p2의 상태를 결정합니다.그래서 뒤의 then 문장은 모두 후자 p1을 대상으로 한다.또 2초가 지나자 p1은rejected로 바뀌어catch 방법이 지정한 리셋 함수를 촉발합니다.
    Resolve나reject를 호출하면 Promise의 매개 변수 함수의 실행이 끝나지 않습니다.
    new Promise((resolve, reject) => {
      resolve(1);
      console.log(2);
    }).then(r => {
      console.log(r);
    });
    // 2
    // 1
    

    resolve (1) 를 호출한 후 뒤에 있는 console.log (2) 는 실행되고 먼저 출력됩니다.즉시resolved의Promise는 이번 이벤트 순환의 끝에서 실행되며, 항상 이번 순환의 동기화 작업보다 늦기 때문입니다.
    일반적으로 Resolve나reject를 호출하면 Promise의 사명이 완성됩니다. 후속 작업은 then 방법에 넣어야 하며, 직접resolve나reject의 뒤에 쓰지 말아야 합니다.그래서 그것들 앞에 리턴 문장을 덧붙이는 것이 좋다. 이렇게 하면 의외의 일이 없을 것이다.
    new Promise((resolve, reject) => {
      return resolve(1);
      //  
      console.log(2);
    })
    

    finally()
    finally 메서드는 Promise 객체의 마지막 상태에 관계없이 수행할 작업을 지정하는 데 사용됩니다.이 방법은 ES2018에서 표준을 도입한 것이다.
    promise
    .then(result => {···})
    .catch(error => {···})
    .finally(() => {···});
    

    promise의 마지막 상태를 막론하고,then이나catch가 지정한 리셋 함수를 실행한 후,finally 방법으로 지정한 리셋 함수를 실행합니다.
    결정된 Promise
    const p = Promise.resolve('foo')
    //  
    const p = new Promise(resolve => resolve('foo'))
    
    const p = Promise.reject(' ');
    //   
    const p = new Promise((resolve, reject) => reject(' '))
    

    상태가 결정된 Promise 인스턴스를 반환합니다.
    실행자 오류
    실행기 내부에서 오류가 발생하면 Promise의 처리 거부 함수가 호출됩니다.
    let promise = new Promise(function(resolve, reject) {
        throw new Error("Explosion!");
    });
    promise.catch(function(error) {
        console.log(error.message);     // "Explosion!"
    });
    

    직렬 연결
    let p1 = new Promise(function(resolve, reject) {
        resolve(42);
    });
    p1.then(function(value) {
        console.log(value);
    }).then(function() {
        console.log("Finished");
    });
    

    직렬 버전 코드 중 p1.then () 의 호출은 두 번째 Promise를 되돌려주고 이 상향 조정에 then () 을 사용합니다.첫 번째 Promise가 결의된 후에만 두 번째 then () 의 완성 처리 함수가 호출됩니다.
    여러 Promise 응답
  • Promise.all () 방법

  • 개별 교체 가능 객체 (예: 배열) 를 매개변수로 수신하고 Promise 를 반환합니다.이 교체 가능한 대상의 요소는 모두 Promise입니다. 이 요소가 모두 완성된 후에만 되돌아오는 Promise가 완성됩니다.
    let p1 = new Promise(function(resolve, reject) {
        resolve(42);
    });
    let p2 = new Promise(function(resolve, reject) {
        resolve(43);
    });
    let p3 = new Promise(function(resolve, reject) {
        resolve(44);
    });
    let p4 = Promise.all([p1, p2, p3]);
    p4.then(function(value) {
        console.log(Array.isArray(value));  // true
        console.log(value[0]);              //  42
        console.log(value[1]);              //  43
        console.log(value[2]);              //  44
    });
    
  • Promise.race () 방법

  • 감시가 필요한 Promise를 포함하는 교체 가능한 대상을 받아들여 새로운 Promise로 되돌려줍니다. 그러나 원본 Promise 중 하나가 해결되면 되돌아오는 Promise는 즉시 해결됩니다.그리고 모든 Promise가 완성되기를 기다리는 Promise.all () 방법이 다르기 때문에 원본 Promise에서 임의로 완성될 때 Promise.race() 메서드가 반환하는 Promise는 응답할 수 있습니다.
    let p1 = Promise.resolve(42);
    let p2 = new Promise(function(resolve, reject) {
        resolve(43);
    });
    let p3 = new Promise(function(resolve, reject) {
        resolve(44);
    });
    let p4 = Promise.race([p1, p2, p3]);
    p4.then(function(value) {
        console.log(value);     // 42
    });
    

    Promise 단점
  • Promise를 취소할 수 없습니다. 새로 만들면 바로 실행되고 중간에 취소할 수 없습니다
  • 리셋 함수를 설정하지 않으면 Promise 내부에서 던진 오류가 외부에 반영되지 않습니다..
  • pending 상태에 있을 때 현재 어느 단계까지 진전되었는지 알 수 없다(막 시작했는지 곧 완성될 것이다)

  • async
  • async는 함수에 비동기적인 조작이 있음을 나타낸다. await는 뒤에 바짝 붙어 있는 표현식이 결과를 기다려야 한다는 것을 나타낸다
  • 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().catch(
      e => console.log(e)
    )
    // Error:  
    
  • async 함수가 되돌아오는 Promise 대상은 내부의 모든 await 명령 뒤에 있는 Promise 대상이 실행될 때까지 기다려야 상태가 바뀔 수 있습니다.return 문장이나 오류가 발생하지 않는 한.즉, async 함수 내부의 비동기 조작이 끝나야만 then 방법이 지정한 리셋 함수를 실행할 수 있다
  • async function asyncTest() {
        let getUrl = await fetch();
        console.log(getUrl);
        let downloadUrl = await download();
        console.log(downloadUrl);
        return 'complete';
    }
    asyncTest().then(console.log)
    
    function fetch() {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                resolve('fetchDone');
            }, 1000);
        });
    }
    
    function download() {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                resolve('downloadDone');
            }, 2000);
        });
    }
    

    위 코드에서 캡처 하나와 다운로드 하나를 시뮬레이션했습니다. 이 두 작업이 모두 끝나야만 then 방법의 console를 실행할 수 있습니다.log.
    await
  • 정상적인 상황에서 await 명령 뒤에 Promise 대상이 있습니다.그렇지 않으면 즉시 resolve의 Promise 객체로 전환됩니다
  • async function f() {
      return await 123;
    }
    f().then(v => console.log(v))
    // 123
    

    await 명령의 매개 변수는 수치 123입니다. 이것은 Promise 대상으로 바뀌고 즉시resolve입니다.
  • 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를 try에...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 명령 뒤에 있는 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 명령 뒤의 비동기 조작, 계발 관계가 존재하지 않으면 동시에 촉발시키는 것이 가장 좋다..
  • //  
    async function test() {
        let [foo, bar] = await Promise.all([getFoo(), getBar()]);
    }
    
    function getFoo() {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                console.log('getFoo');
                resolve()
            }, 2000);
        }) 
    }
    
    function getBar() {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                console.log('getBar');
                resolve()
            }, 2000);
        }) 
    }
    
    test()
    
    //  
    async function test() {
        let fooPromise = getFoo();
        let barPromise = getBar();
        let foo = await fooPromise;
        let bar = await barPromise;
    }
    
  • await 명령은 async 함수에만 사용할 수 있으며, 일반 함수에 사용하면 오류가 발생합니다.
  • async function test() {
        let promises = [function1, function2, function3]
    
        promises.forEach(function (fun) {
            const t = await fun();
            console.log(t)
        });
    }
    
    function function1() {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                resolve('function1')
            }, 2000)
        })
    }
    
    function function2() {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                resolve('function2')
            }, 2000)
        })
    }
    
    function function3() {
        return new Promise(function(resolve, reject) {
            setTimeout(function() {
                resolve('function3')
            }, 2000)
        })
    }
    
    test()
    
  • forEach 방법의 매개 변수를 async 함수로 바꾸면 문제가 있습니다..
  • promises.forEach(async function (fun) {
        const t = await fun();
        console.log(t)
    });
    

    상기 코드가 정상적으로 작동하지 않을 수 있습니다. 왜냐하면 이 때 세 가지 비동기적인 조작은 병행 실행, 즉 동시 실행이 될 것이지 계발 실행이 아니기 때문입니다.정확한 작법은 for순환을 채택하는 것이다.
    for (let i of promises) {
        const t = await i()
        console.log(t)
    }
    
  • 여러 요청이 동시에 실행되기를 원한다면 Promise를 사용할 수 있습니다.all 방법.세 개의 요청이 모두resolved일 때, 아래의 두 가지 쓰기 효과는 같다
  • async function dbFuc(db) {
    async function test() {
        let promises = [function1(), function2(), function3()]
        let results = await Promise.all(promises);
        console.log(results)
    }
    

    참조 링크
    ECMAScript 6 시작
    ES6 이해

    좋은 웹페이지 즐겨찾기