비동기 / 대기 함수 호출


431

내가 이해하는 한, ES7 / ES2016에서 다중 await코드를 코드에 넣는 .then()것은 약속 과 체인을 연결 하는 것과 유사하게 작동하며 , 이는 팔러 렐이 아닌 차례로 실행됩니다. 예를 들어 다음 코드가 있습니다.

await someCall();
await anotherCall();

완료 anotherCall()되었을 때만 호출 되는 것을 올바르게 이해 someCall()합니까? 그것들을 병렬로 호출하는 가장 우아한 방법은 무엇입니까?

Node에서 사용하고 싶습니다. 비동기 라이브러리가있는 솔루션이 있습니까?

편집 : 나는이 질문에 제공된 솔루션에 만족하지 않습니다 : 비동기 생성기에서 약속을 병렬로 대기하지 않기 때문에 속도 저하 : 생성기 를 사용하고보다 일반적인 사용 사례에 대해 묻고 있습니다.


1
@adeneo 틀렸다. Javascript는 자체 컨텍스트 내에서 병렬로 실행되지 않는다.
Blindman67

5
Blindman67 @ - 그것은 적어도 방식으로 영업 수단, 두 개의 비동기 작업을 동시에 실행하는,하지만이 경우,하지, 나는 그들이에서 실행하는 것이되었다 쓰기에 무엇을 의미하는지 시리얼 , 첫 번째 await전체에 대한 첫 번째 기능을 기다릴 것입니다 완전히 두 번째를 실행하기 전에.
adeneo

3
Blindman67 @ - 그것은 단일 스레드,하지만 그 제한은 비동기 방식 적용되지 않습니다, 그들은 그들이, 즉 무엇을 "• 병렬"에 의해 영업 수단을 완료하면 응답을 동시에 실행하고 돌아갑니다.
adeneo

7
@ Blindman67-비동기 / 대기 패턴을 사용하면 기능이 비동기 인 경우에도 직렬로 기능을 실행하여 OP가 요구하는 것이 분명하다고 생각합니다. 따라서 첫 번째는 두 번째가 호출되기 전에 완전히 완료됩니다. 두 함수를 모두 병렬로 호출하는 방법을 요청하고 명확하게 비동기이므로 목표는 동시에 두 개의 아약스 요청을 동시에 수행하는 것입니다. 예를 들어 대부분의 비동기 메소드와 같이 자바 스크립트에서는 전혀 문제가되지 않습니다. 앞서 언급했듯이 네이티브 코드를 실행하고 더 많은 스레드를 사용합니다.
adeneo

3
@ Bergi 이것은 연결된 질문의 복제본이 아닙니다-이것은 특별히 async / await 구문과 native에 관한 것 Promise입니다. 연결된 질문은 생성기 및 수율이있는 블루 버드 라이브러리에 관한 것입니다. 개념적으로는 비슷하지만 구현에는 없습니다.
Iest

답변:


700

당신은 기다릴 수 있습니다 Promise.all():

await Promise.all([someCall(), anotherCall()]);

결과를 저장하려면

let [someResult, anotherResult] = await Promise.all([someCall(), anotherCall()]);

참고 Promise.all빠른 실패 수단이 곧 공급 약속 중 하나가 거부만큼, 다음 전체 물건 거부합니다.

const happy = (v, ms) => new Promise((resolve) => setTimeout(() => resolve(v), ms))
const sad = (v, ms) => new Promise((_, reject) => setTimeout(() => reject(v), ms))

Promise.all([happy('happy', 100), sad('sad', 50)])
  .then(console.log).catch(console.log) // 'sad'

대신, 모든 약속이 이행 또는 거부 될 때까지 기다리려면을 사용할 수 있습니다 Promise.allSettled. Internet Explorer는 기본적으로이 방법을 지원하지 않습니다.

const happy = (v, ms) => new Promise((resolve) => setTimeout(() => resolve(v), ms))
const sad = (v, ms) => new Promise((_, reject) => setTimeout(() => reject(v), ms))

Promise.allSettled([happy('happy', 100), sad('sad', 50)])
  .then(console.log) // [{ "status":"fulfilled", "value":"happy" }, { "status":"rejected", "reason":"sad" }]


78
깨끗하지만 Promise.all의 빠른 실패 동작을 인식하십시오. 함수 중 하나라도 오류가 발생하면 Promise.all은 거부합니다
NoNameProvided

11
async / await로 부분 결과를 잘 처리 할 수 ​​있습니다. stackoverflow.com/a/42158854/2019689
NoNameProvided

131
전문가 팁 : Promise.all ()에서 임의의 수의 결과를 초기화하려면 배열 소멸을 사용하십시오.[result1, result2] = Promise.all([async1(), async2()]);
jonny

10
@jonny이 문제는 빠른 속도로 진행됩니까? 또한 여전히 필요한가 = await Promise.all?
theUtherSide

5
@theUtherSide 당신은 절대적으로 맞습니다-나는 기다림을 포함하지 않았습니다.
jonny

114

TL; DR

Promise.all병렬 함수 호출에 사용 하면 오류가 발생할 때 응답 동작이 올바르게 수행되지 않습니다.


먼저 모든 비동기 호출을 한 번에 실행하고 모든Promise 오브젝트를 확보하십시오 . 둘째, 물체에 사용 await하십시오 Promise. 이렇게하면 첫 번째 Promise해결 을 기다리는 동안 다른 비동기 호출이 계속 진행됩니다. 전반적으로 가장 느린 비동기 호출이있는 한 대기합니다. 예를 들면 다음과 같습니다.

// Begin first call and store promise without waiting
const someResult = someCall();

// Begin second call and store promise without waiting
const anotherResult = anotherCall();

// Now we await for both results, whose async processes have already been started
const finalResult = [await someResult, await anotherResult];

// At this point all calls have been resolved
// Now when accessing someResult| anotherResult,
// you will have a value instead of a promise

JSbin 예 : http://jsbin.com/xerifanima/edit?js,console

주의 사항 : 모든 비동기 호출 await 첫 번째 await호출이 발생하는 호출이 동일한 회선 또는 다른 회선에 있는지 여부는 중요하지 않습니다 . JohnnyHK의 의견을 참조하십시오.


업데이트 : 이 답변은 @bergi의 답변 에 따라 오류 처리의 타이밍이 다르며 오류가 발생할 때 오류가 발생하지 않지만 모든 약속이 실행 된 후에 오류가 발생 하지 않습니다 . @jonny의 팁과 결과를 비교합니다. [result1, result2] = Promise.all([async1(), async2()]), 다음 코드 스 니펫을 확인하십시오.

const correctAsync500ms = () => {
  return new Promise(resolve => {
    setTimeout(resolve, 500, 'correct500msResult');
  });
};

const correctAsync100ms = () => {
  return new Promise(resolve => {
    setTimeout(resolve, 100, 'correct100msResult');
  });
};

const rejectAsync100ms = () => {
  return new Promise((resolve, reject) => {
    setTimeout(reject, 100, 'reject100msError');
  });
};

const asyncInArray = async (fun1, fun2) => {
  const label = 'test async functions in array';
  try {
    console.time(label);
    const p1 = fun1();
    const p2 = fun2();
    const result = [await p1, await p2];
    console.timeEnd(label);
  } catch (e) {
    console.error('error is', e);
    console.timeEnd(label);
  }
};

const asyncInPromiseAll = async (fun1, fun2) => {
  const label = 'test async functions with Promise.all';
  try {
    console.time(label);
    let [value1, value2] = await Promise.all([fun1(), fun2()]);
    console.timeEnd(label);
  } catch (e) {
    console.error('error is', e);
    console.timeEnd(label);
  }
};

(async () => {
  console.group('async functions without error');
  console.log('async functions without error: start')
  await asyncInArray(correctAsync500ms, correctAsync100ms);
  await asyncInPromiseAll(correctAsync500ms, correctAsync100ms);
  console.groupEnd();

  console.group('async functions with error');
  console.log('async functions with error: start')
  await asyncInArray(correctAsync500ms, rejectAsync100ms);
  await asyncInPromiseAll(correctAsync500ms, rejectAsync100ms);
  console.groupEnd();
})();


11
Promise.all 것보다 나에게 훨씬 더 좋은 옵션 같은이 외모 - 당신도 할 수있는 할당 destructuring로 [someResult, anotherResult] = [await someResult, await anotherResult]변경할 경우 const에를 let.
jawj

28
그러나 이것은 여전히 await문장을 연속적으로 실행합니다 . 즉, 첫 번째가 await해결 될 때까지 실행이 일시 중지 된 다음 두 번째로 이동합니다. Promise.all병렬로 실행합니다.
Andru

8
@Haven 감사합니다. 이것이 정답입니다.
Stefan D

87
이 답변은 두 줄을 모두 같은 줄에서 수행한다는 사실과 관련이 없기 때문에 오해의 소지가 있습니다. 중요한 것은 두 비동기 호출이 대기하기 전에 이루어집니다.
JohnnyHK

15
@이 솔루션은와 동일하지 않습니다 Promise.all. 각 요청이 네트워크 호출 인 경우 시작 await someResult하기 전에 해결해야합니다 await anotherResult. 반대로 Promise.allawait통화 중 하나를 해결하기 전에 두 통화를 시작할 수 있습니다.
벤 와인딩

89

최신 정보:

원래의 대답은 약속 거부를 올바르게 처리하는 것을 어렵게하고 때로는 불가능한 경우도 있습니다. 올바른 해결책은 다음을 사용하는 것입니다 Promise.all.

const [someResult, anotherResult] = await Promise.all([someCall(), anotherCall()]);

원래 답변 :

둘 중 하나를 기다리기 전에 두 함수를 모두 호출하십시오.

// Call both functions
const somePromise = someCall();
const anotherPromise = anotherCall();

// Await both promises    
const someResult = await somePromise;
const anotherResult = await anotherPromise;

1
@JeffFischer 나는 분명히 그것을 명확하게하는 의견을 추가했습니다.
Jonathan Potter

9
나는 이것이 가장 순수한 해답이라고 느낀다
Gershom

1
이 답변은 Haven보다 훨씬 명확합니다. 함수 호출이 promise 객체를 반환 await한 다음 실제 값으로 해결 한다는 것이 분명 합니다.
user1032613

3
이것은 한 눈에 작동하는 것처럼 보이지만 처리되지 않은 거부에는 끔찍한 문제가 있습니다. 이것을 사용하지 마십시오!
Bergi

1
@ Bergi 당신이 맞아, 지적 해 주셔서 감사합니다! 더 나은 솔루션으로 답변을 업데이트했습니다.
조나단 포터

24

Promise.all ()없이 병렬로 수행하는 다른 방법이 있습니다.

먼저 숫자를 인쇄하는 두 가지 기능이 있습니다.

function printNumber1() {
   return new Promise((resolve,reject) => {
      setTimeout(() => {
      console.log("Number1 is done");
      resolve(10);
      },1000);
   });
}

function printNumber2() {
   return new Promise((resolve,reject) => {
      setTimeout(() => {
      console.log("Number2 is done");
      resolve(20);
      },500);
   });
}

이것은 순차적입니다.

async function oneByOne() {
   const number1 = await printNumber1();
   const number2 = await printNumber2();
} 
//Output: Number1 is done, Number2 is done

이것은 병렬입니다 :

async function inParallel() {
   const promise1 = printNumber1();
   const promise2 = printNumber2();
   const number1 = await promise1;
   const number2 = await promise2;
}
//Output: Number2 is done, Number1 is done

10

이는 Promise.allSettled () 로 수행 할 수 있으며 , 이는 Promise.all()페일-패스트 동작 과 유사 하지만 실패하지 않습니다.

async function failure() {
    throw "Failure!";
}

async function success() {
    return "Success!";
}

const [failureResult, successResult] = await Promise.allSettled([failure(), success()]);

console.log(failureResult); // {status: "rejected", reason: "Failure!"}
console.log(successResult); // {status: "fulfilled", value: "Success!"}

참고 : 나는 그래서이 제한 브라우저를 지원하는 최첨단 기능입니다 강력하게 이 기능을위한 polyfill을 포함하는 것이 좋습니다.


7

내가 만든 요지 결과, 해결 약속의 몇 가지 다른 방법을 테스트합니다. 작동하는 옵션을 보는 것이 도움이 될 수 있습니다.


요점의 테스트 4와 6은 예상 결과를 반환했습니다. 옵션의 차이점을 설명하는 NoNameProvided의 stackoverflow.com/a/42158854/5683904 를 참조하십시오 .
akraines

1
    // A generic test function that can be configured 
    // with an arbitrary delay and to either resolve or reject
    const test = (delay, resolveSuccessfully) => new Promise((resolve, reject) => setTimeout(() => {
        console.log(`Done ${ delay }`);
        resolveSuccessfully ? resolve(`Resolved ${ delay }`) : reject(`Reject ${ delay }`)
    }, delay));

    // Our async handler function
    const handler = async () => {
        // Promise 1 runs first, but resolves last
        const p1 = test(10000, true);
        // Promise 2 run second, and also resolves
        const p2 = test(5000, true);
        // Promise 3 runs last, but completes first (with a rejection) 
        // Note the catch to trap the error immediately
        const p3 = test(1000, false).catch(e => console.log(e));
        // Await all in parallel
        const r = await Promise.all([p1, p2, p3]);
        // Display the results
        console.log(r);
    };

    // Run the handler
    handler();
    /*
    Done 1000
    Reject 1000
    Done 5000
    Done 10000
    */

p1, p2 및 p3을 설정하면 병렬로 엄격하게 실행되지는 않지만 실행을 유지하지 않으며 컨텍스트 오류를 ​​catch로 잡을 수 있습니다.


2
스택 오버플로에 오신 것을 환영합니다. 귀하의 코드가 질문에 대한 답변을 제공 할 수도 있지만, 그 주변에 컨텍스트를 추가하여 다른 사람들이 그 기능과 그 이유를 알 수 있도록하십시오.
Theo

1

필자의 경우 병렬로 실행하려는 여러 작업이 있지만 해당 작업의 결과와 다른 작업을 수행해야합니다.

function wait(ms, data) {
    console.log('Starting task:', data, ms);
    return new Promise(resolve => setTimeout(resolve, ms, data));
}

var tasks = [
    async () => {
        var result = await wait(1000, 'moose');
        // do something with result
        console.log(result);
    },
    async () => {
        var result = await wait(500, 'taco');
        // do something with result
        console.log(result);
    },
    async () => {
        var result = await wait(5000, 'burp');
        // do something with result
        console.log(result);
    }
]

await Promise.all(tasks.map(p => p()));
console.log('done');

그리고 출력 :

Starting task: moose 1000
Starting task: taco 500
Starting task: burp 5000
taco
moose
burp
done

역동적 인 창조를위한 쿨 (자원 배열)
Michal Miky Jankovský

1

Promise.all ([someCall (), anotherCall ()])을 기다립니다. 이미 언급했듯이 스레드 펜스 (CUDA와 같은 병렬 코드에서 매우 일반적) 역할을하므로 모든 약속을 서로 차단하지 않고 실행할 수는 있지만 ALL이 해결 될 때까지 실행이 계속되지는 않습니다.

공유 할 가치가있는 또 다른 접근 방법은 Node.js 비동기입니다. 또한 API 호출, I / O 작업, 제한된 리소스 사용과 직접 연결되어있는 경우 일반적으로 바람직한 동시성 양을 쉽게 제어 할 수 있습니다 기타

// create a queue object with concurrency 2
var q = async.queue(function(task, callback) {
  console.log('Hello ' + task.name);
  callback();
}, 2);

// assign a callback
q.drain = function() {
  console.log('All items have been processed');
};

// add some items to the queue
q.push({name: 'foo'}, function(err) {
  console.log('Finished processing foo');
});

q.push({name: 'bar'}, function (err) {
  console.log('Finished processing bar');
});

// add some items to the queue (batch-wise)
q.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function(err) {
  console.log('Finished processing item');
});

// add some items to the front of the queue
q.unshift({name: 'bar'}, function (err) {
  console.log('Finished processing bar');
});

중간 기사 autor에 대한 크레딧 ( 더 읽기 )


-5

투표 :

await Promise.all([someCall(), anotherCall()]);

함수를 호출하는 순간을 인식하면 예기치 않은 결과가 발생할 수 있습니다.

// Supposing anotherCall() will trigger a request to create a new User

if (callFirst) {
  await someCall();
} else {
  await Promise.all([someCall(), anotherCall()]); // --> create new User here
}

그러나 다음은 항상 새로운 사용자 생성 요청을 트리거합니다

// Supposing anotherCall() will trigger a request to create a new User

const someResult = someCall();
const anotherResult = anotherCall(); // ->> This always creates new User

if (callFirst) {
  await someCall();
} else {
  const finalResult = [await someResult, await anotherResult]
}

조건 테스트 외부 / 전에 함수를 선언하고 호출했기 때문입니다. 그것들을 else블록으로 감싸보십시오 .
Haven

@Haven : 함수 를 호출 하는 순간 과 대기 를 분리하면 예기치 않은 결과가 발생할 수 있습니다 (예 : 비동기 HTTP 요청).
Hoang Le Anh Tu

-6

도우미 함수 waitAll을 작성하면 더 달콤해질 수 있습니다. 그것은 단지에서 작동 nodejs , 지금은 하지 브라우저 크롬에서.

    //const parallel = async (...items) => {
    const waitAll = async (...items) => {
        //this function does start execution the functions
        //the execution has been started before running this code here
        //instead it collects of the result of execution of the functions

        const temp = [];
        for (const item of items) {
            //this is not
            //temp.push(await item())
            //it does wait for the result in series (not in parallel), but
            //it doesn't affect the parallel execution of those functions
            //because they haven started earlier
            temp.push(await item);
        }
        return temp;
    };

    //the async functions are executed in parallel before passed
    //in the waitAll function

    //const finalResult = await waitAll(someResult(), anotherResult());
    //const finalResult = await parallel(someResult(), anotherResult());
    //or
    const [result1, result2] = await waitAll(someResult(), anotherResult());
    //const [result1, result2] = await parallel(someResult(), anotherResult());

3
아니, 병렬화는 전혀 일어나지 않습니다. for루프마다 순차 약속을 대기하고 배열 결과를 추가한다.
슈 체판 Hołyszewski

나는 이것이 사람들에게 효과가없는 것으로 알고 있습니다. 그래서 node.js와 브라우저에서 테스트했습니다. 테스트는 node.js (v10, v11), firefox에서 전달되며 브라우저 크롬에서는 작동하지 않습니다. 테스트 사례는 gist.github.com/fredyang/ea736a7b8293edf7a1a25c39c7d2fbbf
Fred Yang

2
나는 이것을 믿지 않습니다. 표준에는 for 루프의 다른 반복을 자동으로 병렬 처리 할 수 ​​있다고 말하는 것은 없습니다. 이것은 자바 스크립트가 작동하는 방식이 아닙니다. 루프 코드를 작성하는 방법은, 그것은 의미 이 ". await를 하나의 항목합니다 (await를 expr이), THEN THEN, 온도에 결과를 누르면 다음 항목 (for 루프의 다음 반복을) 가져 가라"각 항목이 완전히은 "대기 테스트에 병렬화가있는 것으로 밝혀 졌다면, 트랜스 파일러가 비표준적인 작업을 수행하거나 버그가있는 것이기 때문일 것입니다.
Szczepan Hołyszewski

@ SzczepanHołyszewski 테스트 케이스를 실행하지 않고 불신에 대한 자신감을 갖게되면 이름을 바꾸고 재 설명하고 추가 의견을 작성하도록 격려합니다. 모든 코드는 평범한 오래된 ES6이며 트랜스 파일링이 필요하지 않습니다.
프레드 양

이것이 왜 그렇게 많이 다운 토트되는지 확실하지 않습니다. 본질적으로 @ user2883596이 준 것과 같은 대답입니다.
Jonathan Sudiaman
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.