Node JS Promise.all 및 forEach


120

비동기 메서드를 노출하는 구조와 같은 배열이 있습니다. 비동기 메서드는 더 많은 비동기 메서드를 노출하는 반환 배열 구조를 호출합니다. 이 구조에서 얻은 값을 저장하기 위해 다른 JSON 개체를 만들고 있으므로 콜백에서 참조를 추적하는 데주의해야합니다.

무차별 대입 솔루션을 코딩했지만 좀 더 관용적이거나 깨끗한 솔루션을 배우고 싶습니다.

  1. 패턴은 n 레벨의 중첩에 대해 반복 가능해야합니다.
  2. Promise.all 또는 유사한 기술을 사용하여 둘러싸는 루틴을 해결할시기를 결정해야합니다.
  3. 모든 요소가 반드시 비동기 호출을 포함하는 것은 아닙니다. 따라서 중첩 된 promise.all에서는 인덱스를 기반으로 JSON 배열 요소에 할당 할 수 없습니다. 그럼에도 불구하고 내포 루틴을 해결하기 전에 모든 속성 할당이 이루어 졌는지 확인하기 위해 중첩 된 forEach에서 promise.all과 같은 것을 사용해야합니다.
  4. bluebird promise lib를 사용하고 있지만 필수 사항은 아닙니다.

다음은 일부 코드입니다.

var jsonItems = [];

items.forEach(function(item){

  var jsonItem = {};
  jsonItem.name = item.name;
  item.getThings().then(function(things){
  // or Promise.all(allItemGetThingCalls, function(things){

    things.forEach(function(thing, index){

      jsonItems[index].thingName = thing.name;
      if(thing.type === 'file'){

        thing.getFile().then(function(file){ //or promise.all?

          jsonItems[index].filesize = file.getSize();

이것은 제가 개선하고 싶은 작업 소스에 대한 링크입니다. github.com/pebanfield/change-view-service/blob/master/src/…
user3205931

1
난 당신이 블루 버드를 사용하고있는 샘플을 참조 블루 버드 실제로 당신의 인생을 만들어 더 쉽게Promise.map(동시) 및 Promise.each이 경우 (연속)도 참고가 Promise.defer되지 않습니다를 - 내 대답 프로그램의 코드가 어떻게하여 피하기 위해 반환 약속을. 약속은 반환 값에 관한 것입니다.
Benjamin Gruenbaum 2015

답변:


368

몇 가지 간단한 규칙으로 매우 간단합니다.

  • 약속을 만들 때마다 then 반환하십시오. 반환 하지 않은 약속은 외부에서 기다리지 않습니다.
  • 여러 약속을 만들 때마다 .all - 그 모든 약속을 기다리는 방법 및 그 중에서 어떤 오류가 침묵하고 있습니다.
  • thens 를 중첩 할 때마다 일반적으로 중간에 반환 할 수 있습니다. - then체인은 일반적으로 대부분의 1 수준의 깊이에있다.
  • IO를 수행 할 때마다 약속이 있어야합니다. 약속에 있어야합니다. 약속에 있어야하거나 완료를 알리기 위해 약속을 사용해야합니다.

그리고 몇 가지 팁 :

  • 매핑은 잘 이루어집니다 .map보다for/push . 함수를 map사용 하여 값을 매핑하는 경우 작업을 하나씩 적용하고 결과를 집계하는 개념을 간결하게 표현할 수 있습니다.
  • 동시성은 무료 인 경우 순차 실행보다 낫습니다. 동시에 실행하고 기다리는 것이 좋습니다.Promise.all 보다 .

자, 시작하겠습니다.

var items = [1, 2, 3, 4, 5];
var fn = function asyncMultiplyBy2(v){ // sample async action
    return new Promise(resolve => setTimeout(() => resolve(v * 2), 100));
};
// map over forEach since it returns

var actions = items.map(fn); // run the function over all items

// we now have a promises array and we want to wait for it

var results = Promise.all(actions); // pass array of promises

results.then(data => // or just .then(console.log)
    console.log(data) // [2, 4, 6, 8, 10]
);

// we can nest this of course, as I said, `then` chains:

var res2 = Promise.all([1, 2, 3, 4, 5].map(fn)).then(
    data => Promise.all(data.map(fn))
).then(function(data){
    // the next `then` is executed after the promise has returned from the previous
    // `then` fulfilled, in this case it's an aggregate promise because of 
    // the `.all` 
    return Promise.all(data.map(fn));
}).then(function(data){
    // just for good measure
    return Promise.all(data.map(fn));
});

// now to get the results:

res2.then(function(data){
    console.log(data); // [16, 32, 48, 64, 80]
});

5
아, 몇 가지 규칙 관점 :-)에서
BERGI

1
@Bergi 누군가는 이러한 규칙의 목록과 약속에 대한 짧은 배경을 작성해야합니다. 아마도 bluebirdjs.com에서 호스팅 할 수 있습니다.
Benjamin Gruenbaum 2015

감사하다고 만 말해서는 안되므로이 예제는보기 좋고 맵 제안이 마음에 들지만 일부 개체에만 비동기 메서드가있는 개체 컬렉션에 대해 어떻게해야합니까? (위의 내 요점 3) 각 요소에 대한 구문 분석 논리를 함수로 추상화 한 다음 비동기 호출 응답에서 해결하거나 비동기 호출이없는 곳에서 간단히 해결할 수 있다는 생각이있었습니다. 말이 돼?
user3205931 jul.

또한 map 함수가 내가 만들고있는 json 객체와 비동기 호출의 결과를 모두 반환해야하므로 어떻게해야할지 확실하지 않습니다. 마지막으로 디렉토리를 걷고 있기 때문에 모든 것이 재귀 적이어야합니다. 구조-나는 여전히 이것을 씹고 있지만 유급 작업이 방해 받고 있습니다 :(
user3205931

2
@ user3205931 약속은 간단하기보다는 간단합니다 . 즉, 다른 것들만큼 친숙하지는 않지만 일단 한번 살펴보면 사용하는 것이 훨씬 낫습니다. 꽉 잡아 당신은 그것을 얻을 것이다 :)
Benjamin Gruenbaum

42

다음은 reduce를 사용하는 간단한 예입니다. 순차적으로 실행되고 삽입 순서를 유지하며 Bluebird가 필요하지 않습니다.

/**
 * 
 * @param items An array of items.
 * @param fn A function that accepts an item from the array and returns a promise.
 * @returns {Promise}
 */
function forEachPromise(items, fn) {
    return items.reduce(function (promise, item) {
        return promise.then(function () {
            return fn(item);
        });
    }, Promise.resolve());
}

다음과 같이 사용하십시오.

var items = ['a', 'b', 'c'];

function logItem(item) {
    return new Promise((resolve, reject) => {
        process.nextTick(() => {
            console.log(item);
            resolve();
        })
    });
}

forEachPromise(items, logItem).then(() => {
    console.log('done');
});

선택적 컨텍스트를 루프로 보내는 것이 유용하다는 것을 알았습니다. 컨텍스트는 선택 사항이며 모든 반복에서 공유됩니다.

function forEachPromise(items, fn, context) {
    return items.reduce(function (promise, item) {
        return promise.then(function () {
            return fn(item, context);
        });
    }, Promise.resolve());
}

약속 기능은 다음과 같습니다.

function logItem(item, context) {
    return new Promise((resolve, reject) => {
        process.nextTick(() => {
            console.log(item);
            context.itemCount++;
            resolve();
        })
    });
}

감사합니다-귀하의 솔루션은 다른 (다양한 npm libs 포함) 그렇지 않은 곳에서 저에게 효과적이었습니다. 이 글을 npm에 게시 했습니까?
SamF 2011

감사합니다. 함수는 모든 약속이 해결되었다고 가정합니다. 거부 된 약속을 어떻게 처리합니까? 또한 가치있는 성공적인 약속을 어떻게 처리합니까?
oyalhi

@oyalhi '컨텍스트'를 사용하고 오류에 매핑 된 거부 된 입력 매개 변수 배열을 추가하는 것이 좋습니다. 일부는 나머지 약속을 모두 무시하고 일부는 무시하기 때문에 실제로 사용 사례에 따라 다릅니다. 반환 된 값의 경우 유사한 접근 방식을 사용할 수도 있습니다.
Steven Spungin

1

나는 같은 상황을 겪었다. 두 개의 Promise.All ()을 사용하여 해결했습니다.

정말 좋은 해결책이라고 생각해서 npm에 게시했습니다 : https://www.npmjs.com/package/promise-foreach

나는 당신의 코드가 다음과 같을 것이라고 생각합니다.

var promiseForeach = require('promise-foreach')
var jsonItems = [];
promiseForeach.each(jsonItems,
    [function (jsonItems){
        return new Promise(function(resolve, reject){
            if(jsonItems.type === 'file'){
                jsonItems.getFile().then(function(file){ //or promise.all?
                    resolve(file.getSize())
                })
            }
        })
    }],
    function (result, current) {
        return {
            type: current.type,
            size: jsonItems.result[0]
        }
    },
    function (err, newList) {
        if (err) {
            console.error(err)
            return;
        }
        console.log('new jsonItems : ', newList)
    })

0

제시된 솔루션에 추가하기 위해 제 경우에는 제품 목록을 위해 Firebase에서 여러 데이터를 가져오고 싶었습니다. 내가 한 방법은 다음과 같습니다.

useEffect(() => {
  const fn = p => firebase.firestore().doc(`products/${p.id}`).get();
  const actions = data.occasion.products.map(fn);
  const results = Promise.all(actions);
  results.then(data => {
    const newProducts = [];
    data.forEach(p => {
      newProducts.push({ id: p.id, ...p.data() });
    });
    setProducts(newProducts);
  });
}, [data]);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.