Node.js 고유의 Promise.all 처리가 병렬 또는 순차적입니까?


173

문서 가 명확하지 않기 때문에이 점을 분명히하고 싶습니다 .

Q1은 : 되어 Promise.all(iterable)순차적으로 또는 병렬로 모두 약속 처리? 더 구체적으로 말하자면, 체인 약속을 실행하는 것과 같습니다.

p1.then(p2).then(p3).then(p4).then(p5)....

또는 모든 알고리즘의 몇 가지 다른 종류의 p1, p2, p3, p4, p5, 등 (병렬) 같은 시간에 호출되는 결과가 모두 해결 (또는 거부) 즉시 반환됩니다?

Q2 : 경우 Promise.all병렬 실행, 반복 가능 sequencially을 실행하는 편리한 방법은 무엇입니까?

참고 : Q 또는 Bluebird를 사용하고 싶지 않지만 모든 기본 ES6 사양을 사용하고 싶습니다.


노드 (V8) 구현 또는 스펙에 대해 묻고 있습니까?
Amit

1
나는 Promise.all그것들을 병렬로 실행한다고 확신 한다.
royhowie 2016 년

@Amit 나는 플래그 node.jsio.js내가 사용하고있는 곳이 때문이다. 예, V8 구현은 가능합니다.
Yanick Rochon 2016 년

9
약속은 "실행"될 수 없습니다. 그들이 될 때 그들은 그들의 작업을 시작 만들어 그들은 단지 결과를 나타냅니다 - - 그리고 당신은 심지어에게 전달하기 전에 병렬로 모든 것을 실행하고 있습니다 Promise.all.
Bergi 2016 년

약속은 창조 순간에 처형됩니다. (비트 코드를 실행하여 확인할 수 있음). 에서는 new Promise(a).then(b); c();A가 먼저 실행되고, 다음, C, B를 차례로. 이러한 약속을 지키는 것은 약속이 아니며 약속이 해결 될 때만 처리합니다.
Mateon1 2016 년

답변:


257

되어 Promise.all(iterable)모든 약속을 실행?

아니요, 약속은 "실행"될 수 없습니다. 그들이 될 때 그들은 그들의 작업을 시작 만들어 그들은 단지 결과를 나타냅니다 - - 그리고 당신은 심지어에게 전달하기 전에 병렬로 모든 것을 실행하고 있습니다 Promise.all.

Promise.all단지 않습니다 await를 여러 약속을. 어떤 순서로 해결되는지 또는 계산이 병렬로 실행되는지는 중요하지 않습니다.

iterable을 순차적으로 실행하는 편리한 방법이 있습니까?

이미 약속이 있다면 많은 것을 할 수 없지만 Promise.all([p1, p2, p3, …])(시퀀스 개념은 없습니다). 그러나 반복 가능한 비동기 함수가 있으면 실제로 순차적으로 실행할 수 있습니다. 기본적으로 당신은에서 얻을 필요가

[fn1, fn2, fn3, …]

fn1().then(fn2).then(fn3).then(…)

그리고 그 해결책은 다음을 사용하는 것입니다 Array::reduce.

iterable.reduce((p, fn) => p.then(fn), Promise.resolve())

1
이 예에서, 호출하려는 약속을 리턴하는 함수의 배열이 반복 가능합니까?
James Reategui

2
@SSH : 이것은 then순서 와 정확히 같습니다. 반환 값은 마지막 fn결과에 대한 약속이며 다른 콜백을 그에 연결할 수 있습니다.
Bergi

1
@wojjas 정확히 같 fn1().then(p2).then(fn3).catch(…습니까? 함수 표현식을 사용할 필요가 없습니다.
Bergi

1
@wojjas 물론는에 retValFromF1전달됩니다 . p2바로 그 일 p2입니다. 물론, 더 많은 것을 원한다면 (추가 변수를 전달하고, 여러 함수를 호출하는 등) 함수 표현식을 사용해야합니다 p2. 배열을 바꾸는 것이 더 쉬울 것입니다.
Bergi

1
@ robe007 예, 그 의미는 iterable는 IS [fn1, fn2, fn3, …]배열
BERGI

62

병행하여

await Promise.all(items.map(async item => { await fetchItem(item) }))

장점 : 더 빠릅니다. 하나라도 실패하더라도 모든 반복이 실행됩니다.

순서대로

for (let i = 0; i < items.length; i++) {
    await fetchItem(items[i])
}

장점 : 루프의 변수는 각 반복마다 공유 할 수 있습니다. 일반적인 명령형 동기 코드처럼 동작합니다.


7
또는 :for (const item of items) await fetchItem(item);
Robert Penner

1
@david_adler 병렬 예제의 장점에 따르면 모든 반복이 실패하더라도 실행됩니다 . 내가 틀리지 않으면 이것은 여전히 ​​빨리 실패합니다. 이 행동을 바꾸려면 다음과 같이 할 수 있습니다 : await Promise.all(items.map(async item => { return await fetchItem(item).catch(e => e) }))
Taimoor

@Taimoor 예는 "빠른 실패"및 Promise.all 후 코드를 계속 실행하지만, 모든 반복은 여전히 실행됩니다 않습니다 codepen.io/mfbx9da4/pen/BbaaXr을
david_adler

이 접근 방식은 async함수가 API 호출 일 때 서버를 DDOS하지 않으려는 경우에 더 좋습니다 . 실행시 발생하는 개별 결과 및 오류를보다 잘 제어 할 수 있습니다. 더 나은 방법으로 어떤 오류를 계속하고 루프를 끊을 지 결정할 수 있습니다.
mandarin

javascript는 단일 스레드이기 때문에 실제로는 스레드를 사용하여 "병렬"로 비동기 요청을 실행하지 않습니다. developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop
david_adler

11

Bergis는 Array.reduce를 사용하여 올바른 길을 찾았습니다.

그러나 실제로 함수를 차례대로 실행하라는 약속을 반환하는 함수를 얻으려면 중첩을 더 추가해야했습니다.

내 실제 사용 사례는 다운 스트림 한계로 인해 차례로 순서대로 전송 해야하는 파일 배열입니다 ...

여기 내가 끝내는 것이 있습니다.

getAllFiles().then( (files) => {
    return files.reduce((p, theFile) => {
        return p.then(() => {
            return transferFile(theFile); //function returns a promise
        });
    }, Promise.resolve()).then(()=>{
        console.log("All files transferred");
    });
}).catch((error)=>{
    console.log(error);
});

이전 답변에서 제안했듯이 다음을 사용하십시오.

getAllFiles().then( (files) => {
    return files.reduce((p, theFile) => {
        return p.then(transferFile(theFile));
    }, Promise.resolve()).then(()=>{
        console.log("All files transferred");
    });
}).catch((error)=>{
    console.log(error);
});

다른 파일을 시작하기 전에 전송이 완료 될 때까지 기다리지 않았으며 첫 번째 파일 전송이 시작되기 전에 "전송 된 모든 파일"텍스트도 표시되었습니다.

내가 뭘 잘못했는지 모르지만 나를 위해 일한 것을 나누고 싶었습니다.

편집 :이 게시물을 작성한 이후로 첫 번째 버전이 작동하지 않는 이유를 이해했습니다. then () 은 약속을 반환하는 함수를 기대합니다 . 따라서 괄호없이 함수 이름을 전달해야합니다! 이제 내 함수는 인수를 원하므로 인수를 사용하지 않는 익명 함수로 감싸 야합니다!


4

@ Bergi의 대답을 자세히 설명하기 위해 (매우 간결하지만 이해하기 까다로운;)

이 코드는 배열의 각 항목을 실행하고 다음 '다음 체인'을 끝에 추가합니다.

function eachorder(prev,order) {
        return prev.then(function() {
          return get_order(order)
            .then(check_order)
            .then(update_order);
        });
    }
orderArray.reduce(eachorder,Promise.resolve());

이해가 되길 바랍니다.


3

재귀 함수를 사용하여 비동기 함수를 사용하여 반복 가능한 iterable을 순차적으로 처리 할 수도 있습니다. 예를 들어 a비동기 함수로 처리 할 배열 이 주어지면 someAsyncFunction():

var a = [1, 2, 3, 4, 5, 6]

function someAsyncFunction(n) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("someAsyncFunction: ", n)
      resolve(n)
    }, Math.random() * 1500)
  })
}

//You can run each array sequentially with: 

function sequential(arr, index = 0) {
  if (index >= arr.length) return Promise.resolve()
  return someAsyncFunction(arr[index])
    .then(r => {
      console.log("got value: ", r)
      return sequential(arr, index + 1)
    })
}

sequential(a).then(() => console.log("done"))


array.prototype.reduce재귀 함수보다 성능 측면에서 사용하는 것이 훨씬 좋습니다
Mateusz Sowiński

@ MateuszSowiński, 각 호출 사이에 1500ms 시간 초과가 있습니다. 이것이 비동기 호출을 순차적으로 수행한다는 점을 고려하면 매우 빠른 비동기 처리 시간에도 이것이 어떻게 관련되는지 알기가 어렵습니다.
Mark Meyer

서로 빠른 속도로 비동기 함수 40 개를 실행해야한다고 가정 해 봅시다. 재귀 함수를 사용하면 메모리가 매우 빨리 막힐 수 있습니다.
Mateusz Sowiński

@ MateuszSowiński, 스택이 여기에 감히 지 않습니다 ... 우리는 각 호출 후에 돌아옵니다. 한 단계로 reduce전체 then()체인 을 구축 해야하는 곳 과 비교 한 다음 실행하십시오.
Mark Meyer

순차 함수의 40 번째 호출에서 함수의 첫 번째 호출은 여전히 ​​순차 함수 체인이 리턴되기를 기다리는 메모리에 있습니다.
Mateusz Sowiński

2

async await를 사용 하면 일련의 약속을 쉽게 순차적으로 실행할 수 있습니다.

let a = [promise1, promise2, promise3];

async function func() {
  for(let i=0; i<a.length; i++){
    await a[i]();
  }  
}

func();

참고 : 위의 구현에서 약속이 거부되면 나머지는 실행되지 않습니다. 모든 약속을 실행하려면 await a[i]();내부 를 감싸십시오.try catch


2

평행

이 예를 참조하십시오

const resolveAfterTimeout = async i => {
  return new Promise(resolve => {
    console.log("CALLED");
    setTimeout(() => {
      resolve("RESOLVED", i);
    }, 5000);
  });
};

const call = async () => {
  const res = await Promise.all([
    resolveAfterTimeout(1),
    resolveAfterTimeout(2),
    resolveAfterTimeout(3),
    resolveAfterTimeout(4),
    resolveAfterTimeout(5),
    resolveAfterTimeout(6)
  ]);
  console.log({ res });
};

call();

코드를 실행하면 6 개의 약속 모두에 대해 "CALLED"콘솔이 표시되고 해결 될 때 시간 초과 후 6 개의 응답마다 동시에 콘솔 링됩니다.


2

NodeJS는 약속을 병렬로 실행하지 않고 단일 스레드 이벤트 루프 아키텍처이므로 동시에 약속을 실행합니다. 다중 코어 CPU를 활용하기 위해 새로운 자식 프로세스를 만들어서 병렬로 작업을 실행할 가능성이 있습니다.

병렬 대 동시

실제로, 무엇 Promise.all 약속 기능을 적절한 대기열에 쌓아서 (이벤트 루프 아키텍처 참조) 동시에 실행하고 (P1, P2 등 호출) 각 결과를 기다린 다음 약속을 모두 해결하십시오. 결과. 거부를 직접 관리하지 않으면 Promise.all은 첫 번째 약속에서 실패합니다.

병렬과 동시의 주요 차이점이 있습니다. 첫 번째 프로세스는 별도의 프로세스에서 정확히 동시에 다른 계산을 실행하고 거기에서 리듬에서 진행되며 다른 하나는 이전 계산을 기다리지 않고 다른 계산을 하나씩 실행합니다. 서로 의존하지 않고 동시에 완료하고 진행하는 계산.

마지막으로, 귀하의 질문에 대답하기 Promise.all위해 병렬 또는 순차적으로 실행되지 않고 동시에 실행됩니다.


이것은 옳지 않습니다. NodeJS는 작업을 병렬로 실행할 수 있습니다. NodeJS에는 작업자 스레드 개념이 있습니다. 기본적으로 작업자 스레드 수는 4입니다. 예를 들어, 암호화 라이브러리를 사용하여 두 값을 해시하는 경우 병렬로 실행할 수 있습니다. 두 개의 작업자 스레드가 작업을 처리합니다. 물론 병렬 처리를 지원하려면 CPU가 멀티 코어 여야합니다.
Shihab

네, 맞습니다. 첫 번째 단락 끝에서 말한 것이지만 자식 프로세스에 대해 이야기했습니다. 물론 그들은 노동자를 운영 할 수 있습니다.
Adrien De Peretti

1

Bergi의 대답은 호출을 동기 화하는 데 도움이되었습니다. 이전 함수가 호출 된 후 각 함수를 호출하는 예제를 아래에 추가했습니다.

function func1 (param1) {
    console.log("function1 : " + param1);
}
function func2 () {
    console.log("function2");
}
function func3 (param2, param3) {
    console.log("function3 : " + param2 + ", " + param3);
}

function func4 (param4) {
    console.log("function4 : " + param4);
}
param4 = "Kate";

//adding 3 functions to array

a=[
    ()=>func1("Hi"),
    ()=>func2(),
    ()=>func3("Lindsay",param4)
  ];

//adding 4th function

a.push(()=>func4("dad"));

//below does func1().then(func2).then(func3).then(func4)

a.reduce((p, fn) => p.then(fn), Promise.resolve());

이것이 원래 질문에 대한 대답입니까?
Giulio Caccin

0

for 루프로 할 수 있습니다.

비동기 함수 반환 약속

async function createClient(client) {
    return await Client.create(client);
}

let clients = [client1, client2, client3];

다음 코드를 작성하면 클라이언트가 병렬로 생성됩니다

const createdClientsArray = yield Promise.all(clients.map((client) =>
    createClient(client);
));

그런 다음 모든 클라이언트가 병렬로 작성됩니다. 그러나 순차적으로 클라이언트를 생성하려면 for 루프를 사용해야합니다

const createdClientsArray = [];
for(let i = 0; i < clients.length; i++) {
    const createdClient = yield createClient(clients[i]);
    createdClientsArray.push(createdClient);
}

모든 클라이언트가 순차적으로 생성됩니다.

행복한 코딩 :)


8
현재 async/ await는 트랜스 파일러 또는 Node 이외의 다른 엔진을 사용하는 경우에만 사용할 수 있습니다 . 또한 실제로 async와 섞어서는 안됩니다 yield. 그들이 트랜스 필러와 똑같이 행동하는 동안 co, 그들은 실제로 매우 다르며 보통 서로를 속이려 서는 안됩니다. 또한 귀하의 답변이 초보자 프로그래머에게는 혼란 스럽기 때문에 이러한 제한 사항을 언급해야합니다.
Yanick Rochon

0

순차적 약속을 해결하기 위해 사용했습니다. 그것이 도움이되는지 확실하지 않지만 이것이 내가하고있는 일입니다.

async function run() {
    for (let val of arr) {
        const res = await someQuery(val)
        console.log(val)
    }
}

run().then().catch()

0

이것은 귀하의 질문의 일부에 대답 할 수 있습니다.

예, 다음과 같이 약속 반환 함수 배열을 연결할 수 있습니다 ... (각 함수의 결과를 다음 함수로 전달) 물론 각 함수에 동일한 인수 (또는 인수 없음)를 전달하도록 편집 할 수 있습니다.

function tester1(a) {
  return new Promise(function(done) {
    setTimeout(function() {
      done(a + 1);
    }, 1000);
  })
}

function tester2(a) {
  return new Promise(function(done) {
    setTimeout(function() {
      done(a * 5);
    }, 1000);
  })
}

function promise_chain(args, list, results) {

  return new Promise(function(done, errs) {
    var fn = list.shift();
    if (results === undefined) results = [];
    if (typeof fn === 'function') {
      fn(args).then(function(result) {
        results.push(result);
        console.log(result);
        promise_chain(result, list, results).then(done);
      }, errs);
    } else {
      done(results);
    }

  });

}

promise_chain(0, [tester1, tester2, tester1, tester2, tester2]).then(console.log.bind(console), console.error.bind(console));


0

NodeJS에서 문제를 해결하려고 시도 하면서이 페이지를 우연히 발견했습니다 : 파일 청크의 재 조립. 기본적으로 : 파일 이름 배열이 있습니다. 하나의 큰 파일을 만들려면 모든 파일을 올바른 순서로 추가해야합니다. 이 작업을 비동기 적으로 수행해야합니다.

노드의 'fs'모듈은 appendFileSync를 제공하지만이 작업 중에 서버를 차단하고 싶지 않습니다. fs.promises 모듈을 사용하고이 것들을 서로 연결하는 방법을 찾고 싶었습니다. 파일 청크에서 읽을 fsPromises.read ()와 대상 파일에 연결하기 위해 fsPromises.appendFile ()의 두 가지 작업이 실제로 필요했기 때문에이 페이지의 예제가 제대로 작동하지 않았습니다. 어쩌면 내가 자바 스크립트에 더 좋으면 이전 답변이 효과가 있었을 것입니다. ;-)

나는 이것을 우연히 발견했다 ... https://css-tricks.com/why-using-reduce-to-sequentially-resolve-promises-works/ ... 그리고 나는 작업 솔루션을 함께 해킹 할 수있었습니다.

TLDR :

/**
 * sequentially append a list of files into a specified destination file
 */
exports.append_files = function (destinationFile, arrayOfFilenames) {
    return arrayOfFilenames.reduce((previousPromise, currentFile) => {
        return previousPromise.then(() => {
            return fsPromises.readFile(currentFile).then(fileContents => {
                return fsPromises.appendFile(destinationFile, fileContents);
            });
        });
    }, Promise.resolve());
};

자스민 단위 테스트는 다음과 같습니다.

const fsPromises = require('fs').promises;
const fsUtils = require( ... );
const TEMPDIR = 'temp';

describe("test append_files", function() {
    it('append_files should work', async function(done) {
        try {
            // setup: create some files
            await fsPromises.mkdir(TEMPDIR);
            await fsPromises.writeFile(path.join(TEMPDIR, '1'), 'one');
            await fsPromises.writeFile(path.join(TEMPDIR, '2'), 'two');
            await fsPromises.writeFile(path.join(TEMPDIR, '3'), 'three');
            await fsPromises.writeFile(path.join(TEMPDIR, '4'), 'four');
            await fsPromises.writeFile(path.join(TEMPDIR, '5'), 'five');

            const filenameArray = [];
            for (var i=1; i < 6; i++) {
                filenameArray.push(path.join(TEMPDIR, i.toString()));
            }

            const DESTFILE = path.join(TEMPDIR, 'final');
            await fsUtils.append_files(DESTFILE, filenameArray);

            // confirm "final" file exists    
            const fsStat = await fsPromises.stat(DESTFILE);
            expect(fsStat.isFile()).toBeTruthy();

            // confirm content of the "final" file
            const expectedContent = new Buffer('onetwothreefourfive', 'utf8');
            var fileContents = await fsPromises.readFile(DESTFILE);
            expect(fileContents).toEqual(expectedContent);

            done();
        }
        catch (err) {
            fail(err);
        }
        finally {
        }
    });
});

누군가에게 도움이되기를 바랍니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.