Promise에 대한 루프를 작성하는 올바른 방법.


116

다음 promise 호출 과 연결된 logger.log (res) 가 반복을 통해 동 기적으로 실행 되도록 루프를 올바르게 구성하는 방법은 무엇입니까? (블루 버드)

db.getUser(email).then(function(res) { logger.log(res); }); // this is a promise

나는 다음과 같은 방법을 시도했다 ( http://blog.victorquinn.com/javascript-promise-while-loop의 방법 )

var Promise = require('bluebird');

var promiseWhile = function(condition, action) {
    var resolver = Promise.defer();

    var loop = function() {
        if (!condition()) return resolver.resolve();
        return Promise.cast(action())
            .then(loop)
            .catch(resolver.reject);
    };

    process.nextTick(loop);

    return resolver.promise;
});

var count = 0;
promiseWhile(function() {
    return count < 10;
}, function() {
    return new Promise(function(resolve, reject) {
        db.getUser(email)
          .then(function(res) { 
              logger.log(res); 
              count++;
              resolve();
          });
    }); 
}).then(function() {
    console.log('all done');
}); 

작동하는 것처럼 보이지만 logger.log (res); 호출 순서를 보장하지 않는다고 생각합니다 .

어떤 제안?


1
코드는 나에게 괜찮아 보입니다 ( loop함수를 사용한 재귀 는 동기 루프를 수행하는 방법입니다). 왜 보장이 없다고 생각합니까?
hugomg

db.getUser (email)는 순서대로 호출됩니다. 그러나 db.getUser () 자체는 promise이므로 순차적으로 호출한다고해서 promise의 비동기 기능으로 인해 'email'에 대한 데이터베이스 쿼리가 순차적으로 실행되는 것은 아닙니다. 따라서 어떤 쿼리가 먼저 완료되는지에 따라 logger.log (res)가 호출됩니다.
user2127480

1
@ user2127480 :하지만 루프의 다음 반복은 약속이 해결 된 후에 만 ​​순차적으로 호출됩니다. 그게 while코드가 작동하는 방식인가요?
Bergi

답변:


78

logger.log (res); 호출 순서를 보장하지 않는다고 생각합니다.

사실 그렇습니다. 해당 명령문은 resolve호출 전에 실행 됩니다.

어떤 제안?

많이. 가장 중요한 것은의 사용입니다 생성 - 약속 - 수동 안티 패턴 만 할 전용 -

promiseWhile(…, function() {
    return db.getUser(email)
             .then(function(res) { 
                 logger.log(res); 
                 count++;
             });
})…

둘째, 그 while기능은 많이 단순화 될 수 있습니다.

var promiseWhile = Promise.method(function(condition, action) {
    if (!condition()) return;
    return action().then(promiseWhile.bind(null, condition, action));
});

셋째, while루프 (클로저 변수 포함)를 사용하지 않고 루프를 사용합니다 for.

var promiseFor = Promise.method(function(condition, action, value) {
    if (!condition(value)) return value;
    return action(value).then(promiseFor.bind(null, condition, action));
});

promiseFor(function(count) {
    return count < 10;
}, function(count) {
    return db.getUser(email)
             .then(function(res) { 
                 logger.log(res); 
                 return ++count;
             });
}, 0).then(console.log.bind(console, 'all done'));

2
죄송합니다. 이 제외 action됩니다 value에 인수로서 promiseFor. 그래서 그렇게 작은 편집을 할 수는 없습니다. 감사합니다. 매우 유용하고 우아합니다.
Gordon

1
@ Roamer-1888 : 용어가 약간 이상 할 수도 있지만 루프는 루프 본문 자체에 바인딩 된 반복 변수 (카운터)가있는 while동안 for루프가 전역 상태를 테스트 한다는 것을 의미 합니다. 사실 저는 루프보다 고정 점 반복처럼 보이는보다 기능적인 접근 방식을 사용했습니다. 코드를 다시 확인하십시오 value. 매개 변수가 다릅니다.
Bergi

2
좋아, 이제 봤어. .bind()새로운을 난독 화 함에 따라 value가독성을 위해 함수를 길게 선택할 수 있다고 생각합니다. 그리고 미안 해요 두께 만하면되는거야 경우 promiseForpromiseWhile공존하지, 그럼 어떻게 하나의 호출 다른를합니까?
Roamer-1888

2
@herve 당신은 그것을 기본적를 생략하고 교체 할 수 있습니다 return …return Promise.resolve(…). 당신이에 대해 추가 보호가 필요한 경우 condition또는 action(같은 예외를 던지는 Promise.method를 제공 ), A의 전체 기능의 몸을 감싸return Promise.resolve().then(() => { … })
BERGI

2
@herve 실제로 Promise.resolve().then(action).…또는 이어야합니다 Promise.resolve(action()).…. 반환 값을 래핑 할 필요가 없습니다.then
Bergi

134

promiseWhen()이 목적과 다른 목적을위한 일반적인 기능을 정말로 원한다면 Bergi의 단순화를 사용하여 꼭 그렇게하세요. 그러나 promise가 작동하는 방식으로 인해 이러한 방식으로 콜백을 전달하는 것은 일반적으로 불필요하며 복잡한 작은 후프를 뛰어 넘도록합니다.

내가 당신이 노력하고 있다고 말할 수있는 한 :

  • 이메일 주소 모음에 대한 일련의 사용자 세부 정보를 비동기식으로 가져옵니다 (적어도 그게 합당한 유일한 시나리오입니다).
  • .then()재귀를 통해 체인을 구축하면 됩니다.
  • 반환 된 결과를 처리 할 때 원래 순서를 유지합니다.

따라서 정의 된 문제는 실제로 Promise Anti-patterns의 "The Collection Kerfuffle"에서 논의 된 문제이며 , 두 가지 간단한 솔루션을 제공합니다.

  • 사용하는 병렬 비동기 호출 Array.prototype.map()
  • 사용 시리얼 비동기 호출 Array.prototype.reduce().

병렬 접근 방식은 (직접적으로) 피하려는 문제를 제공합니다. 즉, 응답의 순서가 불확실합니다. 직렬 접근 방식은 .then()재귀없이 필요한 체인 을 구축합니다 .

function fetchUserDetails(arr) {
    return arr.reduce(function(promise, email) {
        return promise.then(function() {
            return db.getUser(email).done(function(res) {
                logger.log(res);
            });
        });
    }, Promise.resolve());
}

다음과 같이 전화하십시오.

//Compose here, by whatever means, an array of email addresses.
var arrayOfEmailAddys = [...];

fetchUserDetails(arrayOfEmailAddys).then(function() {
    console.log('all done');
});

보시다시피 추악한 외부 var count또는 관련 condition함수 가 필요하지 않습니다 . 제한 (문제에서 10 개)은 전적으로 배열의 길이에 의해 결정됩니다 arrayOfEmailAddys.


16
이것이 선택된 답이어야한다고 생각합니다. 우아하고 재사용 가능한 접근 방식.
ken

1
캐치가 부모에게 다시 전파되는지 아는 사람이 있습니까? 예를 들어 db.getUser가 실패하면 (거부) 오류가 백업을 전파합니까?
wayofthefuture

@wayofthefuture, 아니. 이렇게 생각하면 ..... 역사를 바꿀 수 없습니다.
Roamer-1888

4
답변 해주셔서 감사합니다. 이것은 받아 들여진 대답이어야합니다.
klvs

1
@ Roamer-1888 내 실수, 원래 질문을 잘못 읽었습니다. 저는 (개인적으로) 귀하의 요청이 해결됨에 따라 감소해야하는 초기 목록이 증가하는 솔루션을 찾고있었습니다 (queryMore of a DB). 이 경우 나는 (1) promise 체인의 조건부 확장과 (2) 반환 된 resul의 소비를 상당히 분리하여 생성기로 reduce를 사용하는 아이디어를 찾았습니다.
jhp

40

표준 Promise 객체로 수행하는 방법은 다음과 같습니다.

// Given async function sayHi
function sayHi() {
  return new Promise((resolve) => {
    setTimeout(() => {
      console.log('Hi');
      resolve();
    }, 3000);
  });
}

// And an array of async functions to loop through
const asyncArray = [sayHi, sayHi, sayHi];

// We create the start of a promise chain
let chain = Promise.resolve();

// And append each function in the array to the promise chain
for (const func of asyncArray) {
  chain = chain.then(func);
}

// Output:
// Hi
// Hi (After 3 seconds)
// Hi (After 3 more seconds)

위대한 응답 @youngwerth
잼 Risser

3
이런 식으로 매개 변수를 보내는 방법?
아카 쉬 칸

4
체인에 @khan = chain.then (FUNC) 라인, 당신도 할 수있는 : chain = chain.then(func.bind(null, "...your params here"));chain = chain.then(() => func("your params here"));
youngwerth

9

주어진

  • asyncFn 함수
  • 항목 배열

필수

  • Promise Chaining .then () 's in series (순서대로)
  • 네이티브 es6

해결책

let asyncFn = (item) => {
  return new Promise((resolve, reject) => {
    setTimeout( () => {console.log(item); resolve(true)}, 1000 )
  })
}

// asyncFn('a')
// .then(()=>{return async('b')})
// .then(()=>{return async('c')})
// .then(()=>{return async('d')})

let a = ['a','b','c','d']

a.reduce((previous, current, index, array) => {
  return previous                                    // initiates the promise chain
  .then(()=>{return asyncFn(array[index])})      //adds .then() promise for each item
}, Promise.resolve())

2
asyncJavaScript에서 예약어가 되려는 경우 여기에서 해당 함수의 이름을 명확하게 바꿀 수 있습니다.
hippietrail

또한 중괄호 안에 본문이없는 뚱뚱한 화살표가 단순히 그 표현식이 평가하는 것을 반환하는 경우가 아닙니까? 그러면 코드가 더 간결 해집니다. current사용하지 않는다는 주석을 추가 할 수도 있습니다 .
hippietrail

2
이것이 올바른 방법입니다!
teleme.io

4

이를 해결하는 새로운 방법이 있으며 async / await를 사용하는 것입니다.

async function myFunction() {
  while(/* my condition */) {
    const res = await db.getUser(email);
    logger.log(res);
  }
}

myFunction().then(() => {
  /* do other stuff */
})

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function https://ponyfoo.com/articles/understanding-javascript-async-await


감사합니다. 이것은 프레임 워크 (블루 버드)를 사용하지 않습니다.
Rolf

3

Bergi가 제안한 기능은 정말 좋습니다.

var promiseWhile = Promise.method(function(condition, action) {
      if (!condition()) return;
    return action().then(promiseWhile.bind(null, condition, action));
});

그래도 약속을 사용할 때 의미가있는 작은 추가 작업을하고 싶습니다.

var promiseWhile = Promise.method(function(condition, action, lastValue) {
  if (!condition()) return lastValue;
  return action().then(promiseWhile.bind(null, condition, action));
});

이렇게하면 while 루프를 promise 체인에 포함하고 lastValue로 해결할 수 있습니다 (또한 action ()이 실행되지 않는 경우). 예를보십시오 :

var count = 10;
util.promiseWhile(
  function condition() {
    return count > 0;
  },
  function action() {
    return new Promise(function(resolve, reject) {
      count = count - 1;
      resolve(count)
    })
  },
  count)

3

나는 다음과 같이 만들 것이다.

var request = []
while(count<10){
   request.push(db.getUser(email).then(function(res) { return res; }));
   count++
};

Promise.all(request).then((dataAll)=>{
  for (var i = 0; i < dataAll.length; i++) {

      logger.log(dataAll[i]); 
  }  
});

이런 식으로 dataAll은 기록 할 모든 요소의 정렬 된 배열입니다. 그리고 모든 약속이 완료되면 로그 작업이 수행됩니다.


Promise.all은 동시에 will call promise를 호출합니다. 따라서 완료 순서가 변경 될 수 있습니다. 질문은 연결된 약속을 요구합니다. 따라서 완료 순서를 변경해서는 안됩니다.
canbax

편집 1 : Promise.all을 전혀 호출 할 필요가 없습니다. 약속이 실행되는 한 병렬로 실행됩니다.
canbax

1

async 및 await (es6) 사용 :

function taskAsync(paramets){
 return new Promise((reslove,reject)=>{
 //your logic after reslove(respoce) or reject(error)
})
}

async function fName(){
let arry=['list of items'];
  for(var i=0;i<arry.length;i++){
   let result=await(taskAsync('parameters'));
}

}

0
function promiseLoop(promiseFunc, paramsGetter, conditionChecker, eachFunc, delay) {
    function callNext() {
        return promiseFunc.apply(null, paramsGetter())
            .then(eachFunc)
    }

    function loop(promise, fn) {
        if (delay) {
            return new Promise(function(resolve) {
                setTimeout(function() {
                    resolve();
                }, delay);
            })
                .then(function() {
                    return promise
                        .then(fn)
                        .then(function(condition) {
                            if (!condition) {
                                return true;
                            }
                            return loop(callNext(), fn)
                        })
                });
        }
        return promise
            .then(fn)
            .then(function(condition) {
                if (!condition) {
                    return true;
                }
                return loop(callNext(), fn)
            })
    }

    return loop(callNext(), conditionChecker);
}


function makeRequest(param) {
    return new Promise(function(resolve, reject) {
        var req = https.request(function(res) {
            var data = '';
            res.on('data', function (chunk) {
                data += chunk;
            });
            res.on('end', function () {
                resolve(data);
            });
        });
        req.on('error', function(e) {
            reject(e);
        });
        req.write(param);
        req.end();
    })
}

function getSomething() {
    var param = 0;

    var limit = 10;

    var results = [];

    function paramGetter() {
        return [param];
    }
    function conditionChecker() {
        return param <= limit;
    }
    function callback(result) {
        results.push(result);
        param++;
    }

    return promiseLoop(makeRequest, paramGetter, conditionChecker, callback)
        .then(function() {
            return results;
        });
}

getSomething().then(function(res) {
    console.log('results', res);
}).catch(function(err) {
    console.log('some error along the way', err);
});

0

BlueBird를 사용하는 것은 어떻습니까?

function fetchUserDetails(arr) {
    return Promise.each(arr, function(email) {
        return db.getUser(email).done(function(res) {
            logger.log(res);
        });
    });
}

0

여기에 또 다른 방법이 있습니다 (ES6 w / std Promise). lodash / underscore 유형 종료 기준을 사용합니다 (return === false). doOne ()에서 실행할 옵션에 exitIf () 메서드를 쉽게 추가 할 수 있습니다.

const whilePromise = (fnReturningPromise,options = {}) => { 
    // loop until fnReturningPromise() === false
    // options.delay - setTimeout ms (set to 0 for 1 tick to make non-blocking)
    return new Promise((resolve,reject) => {
        const doOne = () => {
            fnReturningPromise()
            .then((...args) => {
                if (args.length && args[0] === false) {
                    resolve(...args);
                } else {
                    iterate();
                }
            })
        };
        const iterate = () => {
            if (options.delay !== undefined) {
                setTimeout(doOne,options.delay);
            } else {
                doOne();
            }
        }
        Promise.resolve()
        .then(iterate)
        .catch(reject)
    })
};

0

표준 약속 객체를 사용하고 약속이 결과를 반환하도록합니다.

function promiseMap (data, f) {
  const reducer = (promise, x) =>
    promise.then(acc => f(x).then(y => acc.push(y) && acc))
  return data.reduce(reducer, Promise.resolve([]))
}

var emails = []

function getUser(email) {
  return db.getUser(email)
}

promiseMap(emails, getUser).then(emails => {
  console.log(emails)
})

0

먼저 promises (promise array) 배열을 취하고 Promise.all(promisearray).

var arry=['raju','ram','abdul','kruthika'];

var promiseArry=[];
for(var i=0;i<arry.length;i++) {
  promiseArry.push(dbFechFun(arry[i]));
}

Promise.all(promiseArry)
  .then((result) => {
    console.log(result);
  })
  .catch((error) => {
     console.log(error);
  });

function dbFetchFun(name) {
  // we need to return a  promise
  return db.find({name:name}); // any db operation we can write hear
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.