약속을 차례로 (즉, 순서대로) 해결 하시겠습니까?


269

직렬 / 순차 방식으로 파일 배열을 읽는 다음 코드를 고려하십시오. readFiles모든 파일을 순서대로 읽은 후에 만 ​​해결되는 약속을 반환합니다.

var readFile = function(file) {
  ... // Returns a promise.
};

var readFiles = function(files) {
  return new Promise((resolve, reject) => 

    var readSequential = function(index) {
      if (index >= files.length) {
        resolve();
      } else {
        readFile(files[index]).then(function() {
          readSequential(index + 1);
        }).catch(reject);
      }
    };

   readSequential(0); // Start!

  });
};

위의 코드는 작동하지만 순차적으로 발생하기 위해 재귀를 수행하는 것을 좋아하지 않습니다. 이 코드를 다시 작성하여 이상한 것을 사용할 필요가없는 간단한 방법이 있습니까?readSequential 기능 있습니까?

원래 나는을 사용하려고했지만 Promise.all모든 readFile호출이 동시에 발생했기 때문에 원하는 것이 아닙니다 .

var readFiles = function(files) {
  return Promise.all(files.map(function(file) {
    return readFile(file);
  }));
};

2
이전 비동기 작업이 완료되기를 기다려야하는 것은 콜백에서 수행해야합니다. 약속을 사용해도 변경되지 않습니다. 따라서 재귀가 필요합니다.
Barmar

1
참고로, 스택 프레임 빌드가 없으므로 기술적으로 재귀하지 않습니다. 이전 readFileSequential()함수는 다음 함수가 호출되기 전에 이미 반환되었습니다 (비동기식이므로 원래 함수 호출이 이미 반환 된 후에도 완료 됨).
jfriend00

1
@ jfriend00 재귀에는 스택 프레임 누적이 필요하지 않으며 자체 참조 만 가능합니다. 이것은 기술 일뿐입니다.
Benjamin Gruenbaum

3
@ BenjaminGruenbaum-내 요점은 함수 반복 자체가 다음 반복을 시작하는 데 아무런 문제가 없다는 것입니다. 단점은 없으며 실제로 비동기 작업을 시퀀싱하는 효율적인 방법입니다. 따라서 재귀처럼 보이는 것을 피할 이유가 없습니다. 비효율적 인 일부 문제에 대한 재귀 적 솔루션이 있습니다. 이는 그 중 하나가 아닙니다.
jfriend00

1
JavaScript 룸에서의 토론과 요청에 따라이 답변을 편집하여 다른 사람들이 정식으로 지정할 수 있도록했습니다. 당신이 동의하지 않으면 알려 주시기 바랍니다 그리고 나는 그것을 복원하고 별도를 엽니 다.
Benjamin Gruenbaum

답변:


337

2017 업데이트 : 환경에서 지원하는 경우 비동기 기능을 사용합니다.

async function readFiles(files) {
  for(const file of files) {
    await readFile(file);
  }
};

원하는 경우 비동기 생성기를 사용하여 파일이 필요할 때까지 파일을 읽는 것을 연기 할 수 있습니다 (환경에서 지원하는 경우).

async function* readFiles(files) {
  for(const file of files) {
    yield await readFile(file);
  }
};

업데이트 : 다시 생각하면 for 루프를 대신 사용할 수 있습니다.

var readFiles = function(files) {
  var p = Promise.resolve(); // Q() in q

  files.forEach(file =>
      p = p.then(() => readFile(file)); 
  );
  return p;
};

또는 더 축소하여 다음을 수행하십시오.

var readFiles = function(files) {
  return files.reduce((p, file) => {
     return p.then(() => readFile(file));
  }, Promise.resolve()); // initial
};

다른 약속 라이브러리 (예 : Bluebird)에는이를위한 유틸리티 방법이 있습니다.

예를 들어, Bluebird는 다음과 같습니다.

var Promise = require("bluebird");
var fs = Promise.promisifyAll(require("fs"));

var readAll = Promise.resolve(files).map(fs.readFileAsync,{concurrency: 1 });
// if the order matters, you can use Promise.each instead and omit concurrency param

readAll.then(function(allFileContents){
    // do stuff to read files.
});

오늘날 async await를 사용 하지 않는 이유는 없습니다 .


2
@ EmreTapcı, 아뇨. 화살표 함수의 "=>"는 이미 반환을 의미합니다.
최대

TypeScript를 사용한다면 "for in"루프 솔루션이 가장 좋습니다. 재귀 적 수익 감소 약속 예. 첫 번째 통화 반환 유형은 Promise <void>이고 두 번째는 Promise <Promise <void >>입니다. 어떤 생각도 사용하지 않고 입력 할 수 없습니다.
Artur Tagisow

@ArturTagisow TypeScript (최소한 새로운 버전)에는 재귀 유형이 있으며 여기에서 유형을 올바르게 해결 해야 합니다. Promise <Promise <T >>와 같은 것은 "재귀 적으로 동화된다"는 약속이므로 없습니다. Promise.resolve(Promise.resolve(15))와 동일합니다 Promise.resolve(15).
Benjamin Gruenbaum


72

다음은 일련의 작업을 선호하는 방법입니다.

function runSerial() {
    var that = this;
    // task1 is a function that returns a promise (and immediately starts executing)
    // task2 is a function that returns a promise (and immediately starts executing)
    return Promise.resolve()
        .then(function() {
            return that.task1();
        })
        .then(function() {
            return that.task2();
        })
        .then(function() {
            console.log(" ---- done ----");
        });
}

더 많은 작업이있는 사례는 어떻습니까? 10 살처럼?

function runSerial(tasks) {
  var result = Promise.resolve();
  tasks.forEach(task => {
    result = result.then(() => task());
  });
  return result;
}

8
정확한 수의 작업을 모르는 경우는 어떻습니까?
damed

1
그리고 작업 수를 알고 있지만 런타임에만 알고있는 경우는 어떻습니까?
joeytwiddle

10
"약속의 배열을 조작하고 싶지는 않습니다. 약속 스펙에 따라, 약속이 생성 되 자마자 실행이 시작됩니다. 따라서 실제로 원하는 것은 약속 팩토리 배열입니다"고급 실수 # 3 참조 여기 : pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html
edelans

5
라인 노이즈를 줄이려면 다음과 같이 작성할 수도 있습니다.result = result.then(task);
Daniel Buckmaster

1
@DanielBuckmaster 예, 그러나 task ()가 값을 반환하면 다음 호출로 전달되므로주의하십시오. 작업에 선택적 인수가있는 경우 부작용이 발생할 수 있습니다. 현재 코드는 결과를 삼키고 인수없이 다음 작업을 명시 적으로 호출합니다.
JHH

63

이 질문은 오래되었지만 우리는 ES6 및 기능 JavaScript의 세계에 살고 있으므로 어떻게 개선 할 수 있는지 살펴 보겠습니다.

약속은 즉시 실행되므로 약속의 배열을 만들 수는 없으며 모두 동시에 시작됩니다.

대신 약속을 반환하는 함수 배열을 만들어야합니다. 그런 다음 각 기능이 순차적으로 실행되고 약속이 시작됩니다.

우리는 이것을 몇 가지 방법으로 해결할 수 있지만 가장 좋아하는 방법은 reduce .

reduce약속과 함께 사용하면 약간 까다로워 지므로 하나의 라이너를 아래의 작은 소화 가능한 물기로 나눕니다.

이 함수의 본질은 reduce초기 값으로 시작 Promise.resolve([])하거나 빈 배열을 포함하는 약속을 사용하는 것입니다.

이 약속은 다음 reduce과 같은 방법으로 전달 됩니다 promise. 이것이 각 약속을 순차적으로 연결하는 열쇠입니다. 다음 실행 약속 functhen화재 발생시 결과가 연결되고 그 약속이 반환 reduce되어 다음 약속 기능으로 사이클을 실행합니다 .

모든 약속이 실행되면 반환 된 약속에는 각 약속의 모든 결과 배열이 포함됩니다.

ES6 예 (한 라이너)

/*
 * serial executes Promises sequentially.
 * @param {funcs} An array of funcs that return promises.
 * @example
 * const urls = ['/url1', '/url2', '/url3']
 * serial(urls.map(url => () => $.ajax(url)))
 *     .then(console.log.bind(console))
 */
const serial = funcs =>
    funcs.reduce((promise, func) =>
        promise.then(result => func().then(Array.prototype.concat.bind(result))), Promise.resolve([]))

ES6 예 (파손)

// broken down to for easier understanding

const concat = list => Array.prototype.concat.bind(list)
const promiseConcat = f => x => f().then(concat(x))
const promiseReduce = (acc, x) => acc.then(promiseConcat(x))
/*
 * serial executes Promises sequentially.
 * @param {funcs} An array of funcs that return promises.
 * @example
 * const urls = ['/url1', '/url2', '/url3']
 * serial(urls.map(url => () => $.ajax(url)))
 *     .then(console.log.bind(console))
 */
const serial = funcs => funcs.reduce(promiseReduce, Promise.resolve([]))

용법:

// first take your work
const urls = ['/url1', '/url2', '/url3', '/url4']

// next convert each item to a function that returns a promise
const funcs = urls.map(url => () => $.ajax(url))

// execute them serially
serial(funcs)
    .then(console.log.bind(console))

1
아주 좋은, 감사 Array.prototype.concat.bind(result)합니다. 제가 빠뜨린 부분이 수동으로 결과를
내야

우리는 모두 현대 JS에 관한 것이기 때문에 console.log.bind(console)마지막 예제 의 문장은 일반적으로 불필요하다고 생각합니다. 요즘 당신은 통과 할 수 있습니다 console.log. 예 : serial(funcs).then(console.log). 현재 nodejs 및 Chrome에서 테스트되었습니다.
몰 롬비 17

이것은 내 머리를 감싸기가 조금 어려웠지만 감소는 본질적 으로이 일을하고 있습니까? Promise.resolve([]).then((x) => { const data = mockApi('/data/1'); return Promise.resolve(x.concat(data)) }).then((x) => { const data = mockApi('/data/2'); return Promise.resolve(x.concat(data)); });
danecando

@ danecando, 그렇습니다. 정확합니다. 반환에서 Promise.resolve를 삭제할 수도 있습니다. Promise.reject를 호출하지 않으면 반환 된 모든 값이 자동으로 해결됩니다.
joelnet

@joelnet은 danecando의 의견에 따라 다음 표현에서 감소가 더 정확하게 표현되어야한다고 생각합니다. Promise.resolve([]).then(x => someApiCall('url1').then(r => x.concat(r))).then(x => someApiCall('url2').then(r => x.concat(r)))등등
bufferoverflow76

37

ES6에서 간단히 수행하려면 :

function(files) {
  // Create a new empty promise (don't do that with real people ;)
  var sequence = Promise.resolve();

  // Loop over each file, and add on a promise to the
  // end of the 'sequence' promise.
  files.forEach(file => {

    // Chain one computation onto the sequence
    sequence = 
      sequence
        .then(() => performComputation(file))
        .then(result => doSomething(result)); 
        // Resolves for each file, one at a time.

  })

  // This will resolve after the entire chain is resolved
  return sequence;
}

1
밑줄을 사용하는 것 같습니다. files.forEach파일이 배열 인 경우 단순화 할 수 있습니다 .
구스타보 로드리게스

2
글쎄 ... ES5입니다. ES6 방식은 다음과 같습니다 for (file of files) {...}.
구스타보 로드리게스

1
Promise.resolve()실생활에서 이미 해결 된 약속을 만드는 데 사용해서는 안된다고 말합니다 . 왜 안돼? Promise.resolve()보다 깨끗해 보입니다 new Promise(success => success()).
canac

8
@canac 죄송합니다. 단어가 실린 농담이었습니다 ( "빈 약속 .."). Promise.resolve();코드에서 확실히 사용 하십시오.
Shridhar Gupta 2012 년

1
좋은 해결책, 따르기 쉽습니다. 나는 함수에 내 것을 동봉하지 않았다. 그래서 return sequence;내가 넣는 대신 마지막에 해결하기 위해sequence.then(() => { do stuff });
Joe Coyle

25

표준 Node.js 약속을위한 간단한 유틸리티 :

function sequence(tasks, fn) {
    return tasks.reduce((promise, task) => promise.then(() => fn(task)), Promise.resolve());
}

최신 정보

items-promise 는 NPM 패키지를 사용할 준비가되었습니다.


6
나는 이것을 더 자세히 설명하고 싶습니다.
Tyguy7

아래 설명과 함께이 답변의 변형을 제공했습니다. 감사합니다
Sarsaparilla

이것이 정확히 async / await에 액세스 할 수없는 노드 7 이전 환경에서 수행하는 작업입니다. 좋고 깨끗합니다.
JHH

11

나는 많은 순차적 작업을 실행해야 했고이 답변을 사용하여 순차적 작업을 처리하는 기능을 위조했습니다 ...

function one_by_one(objects_array, iterator, callback) {
    var start_promise = objects_array.reduce(function (prom, object) {
        return prom.then(function () {
            return iterator(object);
        });
    }, Promise.resolve()); // initial
    if(callback){
        start_promise.then(callback);
    }else{
        return start_promise;
    }
}

이 함수는 2 개의 인수 + 1 개의 옵션을 취합니다. 첫 번째 논쟁은 우리가 작업 할 배열입니다. 두 번째 인수는 약속 자체를 반환하는 함수 인 작업 자체이며이 약속이 해결 될 때만 다음 작업이 시작됩니다. 세 번째 인수는 모든 작업이 완료되었을 때 실행되는 콜백입니다. 콜백이 전달되지 않으면 함수는 생성 된 약속을 반환하여 끝을 처리 할 수 ​​있습니다.

사용 예는 다음과 같습니다.

var filenames = ['1.jpg','2.jpg','3.jpg'];
var resize_task = function(filename){
    //return promise of async resizing with filename
};
one_by_one(filenames,resize_task );

누군가 시간을 절약하기를 바랍니다.


믿을 수없는 해결책, 그것은 거의 일주일 동안 내가 발견 한 최고의 해결책이었습니다 .... 그것은 매우 잘 설명되어 있으며 논리적 인 내부 이름이 있으며 좋은 예 (더 좋을 수 있음), 나는 그것을 많은 사람들에게 안전하게 부를 수 있습니다 콜백을 설정하는 옵션이 포함되어 있습니다. 단순히 좋은! (그냥 이름을 좀 더 이해 하기 쉬운 이름으로 변경했습니다.) ... 다른 사람들을위한 추천 ... 'Object.keys ( myObject )'를 'objects_array'로 사용하여 객체를 반복 할 수 있습니다.
DavidTaubmann

귀하의 의견에 감사드립니다! 나는 그 이름도 사용하지 않지만 여기서는 더 명확하고 간단하게 만들고 싶었습니다.
Salketer

5

내가 알아낼 수 있었던 가장 좋은 해결책은 bluebird약속이었습니다. Promise.resolve(files).each(fs.readFileAsync);약속이 순차적으로 해결되도록 보장 할 수 있습니다 .


1
더 나은 : Promise.each(filtes, fs.readFileAsync). Btw, 당신은하지 않아도 .bind(fs)됩니까?
Bergi

여기서 아무도 배열과 시퀀스의 차이점을 이해하지 못하는 것 같습니다. 후자는 무제한 / 동적 크기를 의미합니다.
vitaly-t

Javascript의 배열은 C 스타일 언어의 고정 크기 배열과 관련이 없습니다. 그들은 단지에 볼트 숫자 키 관리 개체이며, 어떤 규정 된 크기 또는 제한 (이 없습니다 특히 사용하지 않을 때를 new Array(int). 사전 설정 값은 않습니다 모든 length길이 기반 반복하는 동안 사용되는 몇 지수에 영향을 미치는, 키 - 값 쌍을. 그것은 영을 가지고 실제 배열의 색인 또는 색인 범위에 영향을 미침)
Mike 'Pomax'Kamermans

4

이것은 위의 다른 답변의 약간의 변형입니다. 기본 약속 사용 :

function inSequence(tasks) {
    return tasks.reduce((p, task) => p.then(task), Promise.resolve())
}

설명

이러한 작업이있는 경우 [t1, t2, t3]위와 같습니다 Promise.resolve().then(t1).then(t2).then(t3). 감소 동작입니다.

사용하는 방법

먼저 작업 목록을 구성해야합니다! 작업은 인수를받지 않는 함수입니다. 함수에 인수를 전달해야하는 경우 bind또는 다른 방법을 사용하여 작업을 만듭니다. 예를 들면 다음과 같습니다.

var tasks = files.map(file => processFile.bind(null, file))
inSequence(tasks).then(...)

4

내가 선호하는 솔루션 :

function processArray(arr, fn) {
    return arr.reduce(
        (p, v) => p.then((a) => fn(v).then(r => a.concat([r]))),
        Promise.resolve([])
    );
}

여기에 출판 된 다른 것들과 근본적으로 다르지 않지만 :

  • 일련의 항목 기능을 적용합니다
  • 결과 배열로 해석
  • 비동기 / 대기 필요 없음 (2017 년경 지원은 여전히 ​​제한적 임)
  • 화살표 기능을 사용합니다. 좋고 간결한

사용법 예 :

const numbers = [0, 4, 20, 100];
const multiplyBy3 = (x) => new Promise(res => res(x * 3));

// Prints [ 0, 12, 60, 300 ]
processArray(numbers, multiplyBy3).then(console.log);

합리적인 최신 Chrome (v59) 및 NodeJS (v8.1.2)에서 테스트되었습니다.


3

를 사용 Array.prototype.reduce하고 약속을 함수로 감싸 야합니다. 그렇지 않으면 이미 실행 중입니다!

// array of Promise providers

const providers = [
  function(){
     return Promise.resolve(1);
  },
  function(){
     return Promise.resolve(2);
  },
  function(){
     return Promise.resolve(3);
  }
]


const inSeries = function(providers){

  const seed = Promise.resolve(null); 

  return providers.reduce(function(a,b){
      return a.then(b);
  }, seed);
};

좋고 쉬운 ... 당신은 성능 등을 위해 동일한 시드를 재사용 할 수 있어야합니다

reduce를 사용할 때 빈 배열이나 요소가 하나 뿐인 배열지키는 것이 중요 하므로이 기술이 가장 좋습니다.

   const providers = [
      function(v){
         return Promise.resolve(v+1);
      },
      function(v){
         return Promise.resolve(v+2);
      },
      function(v){
         return Promise.resolve(v+3);
      }
    ]

    const inSeries = function(providers, initialVal){

        if(providers.length < 1){
            return Promise.resolve(null)
        }

        return providers.reduce((a,b) => a.then(b), providers.shift()(initialVal));
    };

그런 다음 다음과 같이 호출하십시오.

inSeries(providers, 1).then(v => {
   console.log(v);  // 7
});

2

Promise 객체에서이 간단한 방법을 만들었습니다.

Promise.sequence 메서드를 만들어 Promise 개체에 추가

Promise.sequence = function (chain) {
    var results = [];
    var entries = chain;
    if (entries.entries) entries = entries.entries();
    return new Promise(function (yes, no) {
        var next = function () {
            var entry = entries.next();
            if(entry.done) yes(results);
            else {
                results.push(entry.value[1]().then(next, function() { no(results); } ));
            }
        };
        next();
    });
};

용법:

var todo = [];

todo.push(firstPromise);
if (someCriterium) todo.push(optionalPromise);
todo.push(lastPromise);

// Invoking them
Promise.sequence(todo)
    .then(function(results) {}, function(results) {});

Promise 오브젝트에 대한이 확장에 대한 가장 좋은 점은 약속 스타일과 일치한다는 것입니다. Promise.all과 Promise.sequence는 같은 방식으로 호출되지만 의미가 다릅니다.

주의

순차적 인 약속 실행은 일반적으로 약속을 사용하는 좋은 방법이 아닙니다. 일반적으로 Promise.all을 사용하는 것이 좋으며 브라우저가 가능한 빨리 코드를 실행하도록하십시오. 그러나 자바 스크립트를 사용하여 모바일 앱을 작성할 때와 같은 실제 사용 사례가 있습니다.


아니요, Promise.all와 (과) 비교할 수 없습니다 Promise.sequence. 하나는 반복 가능한 약속을, 다른 하나는 약속을 반환하는 다양한 함수를 취합니다.
Bergi


반복자가 필요하다는 것을 몰랐습니다. 그래도 다시 쓸 수있을 정도로 쉬워야합니다. 이것이 약속 생성자 반 패턴 인 이유를 자세히 설명해 주시겠습니까? 나는 당신의 게시물을 읽었습니다 : stackoverflow.com/a/25569299/1667011
frodeborli

@ Bergi 반복기를 지원하도록 코드를 업데이트했습니다. 나는 이것이 반 패턴이라는 것을 여전히 알지 못한다. 안티 패턴은 일반적으로 코딩 실수를 피하기위한 지침으로 간주되며, 이러한 지침을 위반하는 (라이브러리) 함수를 작성하는 것이 완벽하게 유효합니다.
frodeborli

예, 라이브러리 기능으로 간주해도 괜찮습니다. 그러나이 경우 reduce벤자민의 대답과 비슷한 것이 훨씬 간단합니다.
Bergi

2

promiseFactories List를 얻는이 기능을 사용할 수 있습니다.

function executeSequentially(promiseFactories) {
    var result = Promise.resolve();
    promiseFactories.forEach(function (promiseFactory) {
        result = result.then(promiseFactory);
    });
    return result;
}

Promise Factory는 Promise를 반환하는 간단한 함수입니다.

function myPromiseFactory() {
    return somethingThatCreatesAPromise();
}

약속 팩토리는 요청 될 때까지 약속을 만들지 않기 때문에 작동합니다. 당시 함수와 같은 방식으로 작동합니다. 실제로는 동일합니다!

당신은 약속의 배열을 전혀 조작하고 싶지 않습니다. 약속 사양에 따라 약속이 생성 되 자마자 실행이 시작됩니다. 그래서 당신이 정말로 원하는 것은 약속 공장의 배열입니다 ...

Promises에 대한 자세한 내용을 보려면 다음 링크를 확인하십시오. https://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html


2

내 대답은 https://stackoverflow.com/a/31070150/7542429 기반입니다 .

Promise.series = function series(arrayOfPromises) {
    var results = [];
    return arrayOfPromises.reduce(function(seriesPromise, promise) {
      return seriesPromise.then(function() {
        return promise
        .then(function(result) {
          results.push(result);
        });
      });
    }, Promise.resolve())
    .then(function() {
      return results;
    });
  };

이 솔루션은 결과를 Promise.all ()과 같은 배열로 반환합니다.

용법:

Promise.series([array of promises])
.then(function(results) { 
  // do stuff with results here
});

2

나는 @ joelnet의 대답을 정말로 좋아했지만 나에게 그 코딩 스타일은 소화하기가 약간 어려워서 같은 솔루션을 더 읽기 쉬운 방식으로 표현하는 방법을 알아 내려고 며칠을 보냈습니다. 다른 구문과 주석 만 사용하십시오.

// first take your work
const urls = ['/url1', '/url2', '/url3', '/url4']

// next convert each item to a function that returns a promise
const functions = urls.map((url) => {
  // For every url we return a new function
  return () => {
    return new Promise((resolve) => {
      // random wait in milliseconds
      const randomWait = parseInt((Math.random() * 1000),10)
      console.log('waiting to resolve in ms', randomWait)
      setTimeout(()=>resolve({randomWait, url}),randomWait)
    })
  }
})


const promiseReduce = (acc, next) => {
  // we wait for the accumulator to resolve it's promise
  return acc.then((accResult) => {
    // and then we return a new promise that will become
    // the new value for the accumulator
    return next().then((nextResult) => {
      // that eventually will resolve to a new array containing
      // the value of the two promises
      return accResult.concat(nextResult)
    })
  })
};
// the accumulator will always be a promise that resolves to an array
const accumulator = Promise.resolve([])

// we call reduce with the reduce function and the accumulator initial value
functions.reduce(promiseReduce, accumulator)
  .then((result) => {
    // let's display the final value here
    console.log('=== The final result ===')
    console.log(result)
  })

2

Bergi가 알았 듯이, 가장 명확하고 명확한 솔루션은 아래 코드를 사용하는 것입니다.

const BlueBird = require('bluebird');
BlueBird.each(files, fs.readFileAsync);

2

먼저 약속을 만들 때 약속이 실행된다는 것을 이해해야합니다.
예를 들어 코드가있는 경우 :

["a","b","c"].map(x => returnsPromise(x))

다음과 같이 변경해야합니다.

["a","b","c"].map(x => () => returnsPromise(x))

그런 다음 약속을 순차적으로 연결해야합니다.

["a", "b", "c"].map(x => () => returnsPromise(x))
    .reduce(
        (before, after) => before.then(_ => after()),
        Promise.resolve()
    )

를 실행 after()하면 약속 시간이되었을 때만 약속이 작성되고 실행되도록합니다.


1

다음 코드를 사용하여 Promise 개체를 확장합니다. 약속 거부를 처리하고 결과 배열을 반환합니다.

암호

/*
    Runs tasks in sequence and resolves a promise upon finish

    tasks: an array of functions that return a promise upon call.
    parameters: an array of arrays corresponding to the parameters to be passed on each function call.
    context: Object to use as context to call each function. (The 'this' keyword that may be used inside the function definition)
*/
Promise.sequence = function(tasks, parameters = [], context = null) {
    return new Promise((resolve, reject)=>{

        var nextTask = tasks.splice(0,1)[0].apply(context, parameters[0]); //Dequeue and call the first task
        var output = new Array(tasks.length + 1);
        var errorFlag = false;

        tasks.forEach((task, index) => {
            nextTask = nextTask.then(r => {
                output[index] = r;
                return task.apply(context, parameters[index+1]);
            }, e=>{
                output[index] = e;
                errorFlag = true;
                return task.apply(context, parameters[index+1]);
            });
        });

        // Last task
        nextTask.then(r=>{
            output[output.length - 1] = r;
            if (errorFlag) reject(output); else resolve(output);
        })
        .catch(e=>{
            output[output.length - 1] = e;
            reject(output);
        });
    });
};

function functionThatReturnsAPromise(n) {
    return new Promise((resolve, reject)=>{
        //Emulating real life delays, like a web request
        setTimeout(()=>{
            resolve(n);
        }, 1000);
    });
}

var arrayOfArguments = [['a'],['b'],['c'],['d']];
var arrayOfFunctions = (new Array(4)).fill(functionThatReturnsAPromise);


Promise.sequence(arrayOfFunctions, arrayOfArguments)
.then(console.log)
.catch(console.error);

1

원하는 경우 다음과 같이 reduce를 사용하여 순차적 약속을 할 수 있습니다.

[2,3,4,5,6,7,8,9].reduce((promises, page) => {
    return promises.then((page) => {
        console.log(page);
        return Promise.resolve(page+1);
    });
  }, Promise.resolve(1));

항상 순차적으로 작동합니다.


1

최신 ES 사용 :

const series = async (tasks) => {
  const results = [];

  for (const task of tasks) {
    const result = await task;

    results.push(result);
  }

  return results;
};

//...

const readFiles = await series(files.map(readFile));

1

Async / Await 사용 (ES7을 지원하는 경우)

function downloadFile(fileUrl) { ... } // This function return a Promise

async function main()
{
  var filesList = [...];

  for (const file of filesList) {
    await downloadFile(file);
  }
}

async / await에 forEach 루프에서 실행하는 데 문제 가 있으므로 for루프 를 사용해야합니다.forEach

비동기 / 대기없이 (Promise 사용)

function downloadFile(fileUrl) { ... } // This function return a Promise

function downloadRecursion(filesList, index)
{
  index = index || 0;
  if (index < filesList.length)
  {
    downloadFile(filesList[index]).then(function()
    {
      index++;
      downloadRecursion(filesList, index); // self invocation - recursion!
    });
  }
  else
  {
    return Promise.resolve();
  }
}

function main()
{
  var filesList = [...];
  downloadRecursion(filesList);
}

2
for for each는 권장하지 않습니다.
Marcelo Agimóvel

MarceloAgimóvel @ - 나는하지 작업에 솔루션을 업데이트 한 forEach(에 따라 )
길 Epshtain

0

질문의 제목, "서로 약속을 차례로 해결합니까 (예 : 순서대로)?"에 따라 OP는 순차적 호출 자체 보다 정산 약속의 순차적 처리에 더 관심이 있음을 이해할 수 있습니다. .

이 답변이 제공됩니다 :

  • 응답을 순차적으로 처리하는 데 순차적 호출이 필요하지 않음을 보여줍니다.
  • 1 년이 지난 후에도 여전히 관심이있는 경우 OP를 포함하여이 페이지 방문자에게 실행 가능한 대체 패턴을 노출합니다.
  • OP의 주장에도 불구하고, 동시에 전화를 걸고 싶지 않다는 주장도 있지만, 이는 제목이 의미하는대로 순차적으로 응답을 처리하려는 요구에 기반한 가정 일 수도 있습니다.

동시 통화를 원치 않으면 순차 통화 등을 포괄하는 Benjamin Gruenbaum의 답변을 참조하십시오.

그러나 동시 호출을 허용하고 순차적으로 응답을 처리하는 패턴에 관심이있는 경우 (성능 향상을 위해) 계속 읽으십시오.

Promise.all(arr.map(fn)).then(fn)(내가 여러 번 한 것처럼) Promise lib의 멋진 설탕 (특히 Bluebird 's) 을 사용해야한다고 생각 하고 싶지만 ( 이 기사에arr.map(fn).reduce(fn) 의지 하여) 패턴은 다음과 같은 이점이 있습니다.

  • 프리미티브 버전의 jQuery조차도 모든 약속 라이브러리와 함께 작동합니다. .then() .
  • 한 줄 모드로 원하는 것을 건너 뛰거나 오류를 건너 뛸 수있는 유연성을 제공합니다.

여기에 쓰여진 것입니다 Q.

var readFiles = function(files) {
    return files.map(readFile) //Make calls in parallel.
    .reduce(function(sequence, filePromise) {
        return sequence.then(function() {
            return filePromise;
        }).then(function(file) {
            //Do stuff with file ... in the correct sequence!
        }, function(error) {
            console.log(error); //optional
            return sequence;//skip-over-error. To stop-on-error, `return error` (jQuery), or `throw  error` (Promises/A+).
        });
    }, Q()).then(function() {
        // all done.
    });
};

참고 : 하나의 조각 만 Q() Q에 고유합니다. jQuery의 경우 readFile ()이 jQuery 약속을 반환하는지 확인해야합니다. A + 라이브러리를 사용하면 외국 약속이 동화됩니다.

열쇠는 여기에 감소의 인 sequence시퀀스 약속, 취급readFile그들의 창조를 약속하지만.

그리고 일단 당신이 그것을 흡수하면, .map()무대가 실제로 필요하지 않다는 것을 깨달았을 때 약간의 생각이 날 것입니다 ! 전체 작업, 병렬 호출 및 올바른 순서로 직렬 처리를 reduce()단독 으로 수행 할 수 있으며 다음 과 같은 유연성이 추가됩니다.

  • 한 줄만 이동하여 병렬 비동기 호출에서 직렬 비동기 호출로 변환-개발 중에 유용 할 수 있습니다.

Q다시 여기 있습니다 .

var readFiles = function(files) {
    return files.reduce(function(sequence, f) {
        var filePromise = readFile(f);//Make calls in parallel. To call sequentially, move this line down one.
        return sequence.then(function() {
            return filePromise;
        }).then(function(file) {
            //Do stuff with file ... in the correct sequence!
        }, function(error) {
            console.log(error); //optional
            return sequence;//Skip over any errors. To stop-on-error, `return error` (jQuery), or `throw  error` (Promises/A+).
        });
    }, Q()).then(function() {
        // all done.
    });
};

이것이 기본 패턴입니다. 데이터 (예 : 파일 또는 일부 변환)를 호출자에게 전달하려면 약간의 변형이 필요합니다.


OP의 의도에 반하는 질문에 대답하는 것은 좋은 생각이 아니라고 생각합니다…
Bergi

1
이것은 sequence.then(() => filePromise)반 패턴입니다. 오류가 발생하자마자 전파되지 않으며 unhandledRejection이를 지원하는 라이브러리에서 생성 됩니다. 오히려 Q.all([sequence, filePromise])또는을 사용해야합니다 $.when(sequence, filePromise). 분명히,이 동작 오류를 무시하거나 건너 뛰려고 할 때 원하는 것일 수 있지만 적어도 단점으로 언급해야합니다.
Bergi

@Bergi, 나는 OP가 개입하여 이것이 그의 의도와 실제로 반대되는지 아닌지를 판단하기를 바라고있다. 그렇지 않다면, 내가 생각한 답을 삭제하고 그 동안 내 입장을 정당화하기를 바랍니다. 적절한 피드백을 제공 할 수 있도록 진지하게 고려해 주셔서 감사합니다. 안티 패턴에 대해 자세히 설명하거나 참조하십시오. 기본 패턴을 찾은 기사 에도 동일하게 적용됩니까 ?
Roamer-1888

1
그렇습니다. 그의 코드의 세 번째 버전 (즉, "병렬 및 순차적")은 동일한 문제를 가지고 있습니다. "반 패턴"은 정교한 오류 처리가 필요하며 핸들러를 비동기 적으로 연결하기 쉽습니다.unhandledRejection 이벤트 합니다. Bluebird에서는 sequence.return(filePromise)동일한 동작을 갖지만 거부를 잘 처리하는 을 사용하여이 문제를 해결할 수 있습니다 . 나는 어떤 참조도 모르고, 나는 단지 그것을 생각해 냈습니다. "(anti) pattern"은 아직 이름이 없다고 생각합니다.
Bergi

1
@Bergi, 당신은 분명히 뭔가를 볼 수 있습니다 그럴 수 없어 :(이 새로운 안티 패턴 요구 어딘가를 기록 할 경우 궁금해?
로머-1888

0

귀하의 접근 방식은 나쁘지 않지만 두 가지 문제가 있습니다. 오류를 삼키고 명시 적 약속 건설 반 패턴을 사용합니다.

이 두 가지 문제를 모두 해결하고 동일한 전략을 그대로 유지하면서 코드를 더 깨끗하게 만들 수 있습니다.

var Q = require("q");

var readFile = function(file) {
  ... // Returns a promise.
};

var readFiles = function(files) {
  var readSequential = function(index) {
    if (index < files.length) {
      return readFile(files[index]).then(function() {
        return readSequential(index + 1);
      });
    }
  };

  // using Promise.resolve() here in case files.length is 0
  return Promise.resolve(readSequential(0)); // Start!
};

0

누군가가 CRUD 작업을 수행 할 때 약속 된 순차 해결 방법을 엄격하게 보장하는 방법이 필요한 경우 다음 코드를 기본으로 사용할 수도 있습니다.

Promise를 설명하고 각 함수를 호출하기 전에 'return'을 추가하고이 예제를 기본으로 사용하는 한 다음 .then () 함수 호출은 이전 함수를 완료 한 후에 지속적으로 시작됩니다.

getRidOfOlderShoutsPromise = () => {
    return readShoutsPromise('BEFORE')
    .then(() => {
        return deleteOlderShoutsPromise();
    })
    .then(() => {
        return readShoutsPromise('AFTER')
    })
    .catch(err => console.log(err.message));
}

deleteOlderShoutsPromise = () => {
    return new Promise ( (resolve, reject) => {
        console.log("in deleteOlderShouts");
        let d = new Date();
        let TwoMinuteAgo = d - 1000 * 90 ;
        All_Shouts.deleteMany({ dateTime: {$lt: TwoMinuteAgo}}, function(err) {
            if (err) reject();
            console.log("DELETED OLDs at "+d);
            resolve();        
        });
    });
}

readShoutsPromise = (tex) => {
    return new Promise( (resolve, reject) => {
        console.log("in readShoutsPromise -"+tex);
        All_Shouts
        .find({})
        .sort([['dateTime', 'ascending']])
        .exec(function (err, data){
            if (err) reject();
            let d = new Date();
            console.log("shouts "+tex+" delete PROMISE = "+data.length +"; date ="+d);
            resolve(data);
        });    
    });
}

0

일련의 약속에 배열 푸시 및 팝 방법을 사용할 수 있습니다. 추가 데이터가 필요할 때 새로운 약속을 추진할 수도 있습니다. 이것은 코드입니다. React Infinite 로더에서 일련의 페이지를로드하는 데 사용합니다.

var promises = [Promise.resolve()];

function methodThatReturnsAPromise(page) {
	return new Promise((resolve, reject) => {
		setTimeout(() => {
			console.log(`Resolve-${page}! ${new Date()} `);
			resolve();
		}, 1000);
	});
}

function pushPromise(page) {
	promises.push(promises.pop().then(function () {
		return methodThatReturnsAPromise(page)
	}));
}

pushPromise(1);
pushPromise(2);
pushPromise(3);


0

대부분의 답변은 모든 약속의 결과를 개별적으로 포함하지 않으므로 누군가이 특정 행동을 찾고있는 경우 재귀를 사용하는 가능한 솔루션입니다.

다음 스타일을 따릅니다 Promise.all.

  • .then()콜백 에서 결과 배열을 반환합니다 .

  • 일부 약속이 실패하면 .catch()콜백 으로 즉시 반환 됩니다.

const promiseEach = (arrayOfTasks) => {
  let results = []
  return new Promise((resolve, reject) => {
    const resolveNext = (arrayOfTasks) => {
      // If all tasks are already resolved, return the final array of results
      if (arrayOfTasks.length === 0) return resolve(results)

      // Extract first promise and solve it
      const first = arrayOfTasks.shift()

      first().then((res) => {
        results.push(res)
        resolveNext(arrayOfTasks)
      }).catch((err) => {
        reject(err)
      })
    }
    resolveNext(arrayOfTasks)
  })
}

// Lets try it 😎

const promise = (time, shouldThrowError) => new Promise((resolve, reject) => {
  const timeInMs = time * 1000
  setTimeout(()=>{
    console.log(`Waited ${time} secs`)
    if (shouldThrowError) reject(new Error('Promise failed'))
    resolve(time)
  }, timeInMs)
})

const tasks = [() => promise(1), () => promise(2)]

promiseEach(tasks)
  .then((res) => {
    console.log(res) // [1, 2]
  })
  // Oops some promise failed
  .catch((error) => {
    console.log(error)
  })

tasks배열 선언 에 대한 참고 사항 :

이 경우 다음과 같은 표기법을 사용할 수 없습니다 Promise.all.

const tasks = [promise(1), promise(2)]

그리고 우리는 사용해야합니다 :

const tasks = [() => promise(1), () => promise(2)]

그 이유는 JavaScript가 약속을 선언 한 직후에 약속을 실행하기 때문입니다. 와 같은 메소드를 사용하면 Promise.all모든 상태가 fulfilled또는인지 확인 rejected하지만 실행 자체를 시작하지는 않습니다. 를 사용하여 () => promise()호출 될 때까지 실행을 중지합니다.


0
(function() {
  function sleep(ms) {
    return new Promise(function(resolve) {
      setTimeout(function() {
        return resolve();
      }, ms);
    });
  }

  function serial(arr, index, results) {
    if (index == arr.length) {
      return Promise.resolve(results);
    }
    return new Promise(function(resolve, reject) {
      if (!index) {
        index = 0;
        results = [];
      }
      return arr[index]()
        .then(function(d) {
          return resolve(d);
        })
        .catch(function(err) {
          return reject(err);
        });
    })
      .then(function(result) {
        console.log("here");
        results.push(result);
        return serial(arr, index + 1, results);
      })
      .catch(function(err) {
        throw err;
      });
  }

  const a = [5000, 5000, 5000];

  serial(a.map(x => () => sleep(x)));
})();

여기서 핵심은 슬립 기능을 호출하는 방법입니다. 약속 배열 대신 약속을 반환하는 함수 배열을 전달해야합니다.


-1

이는 spex.sequence 구현을 기반으로 동적 / 무한 순서를 지원하면서보다 일반적인 방식으로 약속 순서를 처리하는 방법을 확장하는 것입니다.

var $q = require("q");
var spex = require('spex')($q);

var files = []; // any dynamic source of files;

var readFile = function (file) {
    // returns a promise;
};

function source(index) {
    if (index < files.length) {
        return readFile(files[index]);
    }
}

function dest(index, data) {
    // data = resolved data from readFile;
}

spex.sequence(source, dest)
    .then(function (data) {
        // finished the sequence;
    })
    .catch(function (error) {
        // error;
    });

이 솔루션은 모든 크기의 시퀀스에서 작동 할뿐만 아니라 데이터 조절 및로드 밸런싱 을 쉽게 추가 할 수 있습니다 .

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