Promise 배열을 순차적으로 실행하려면 어떻게해야합니까?


81

순차적으로 실행해야하는 일련의 약속이 있습니다.

var promises = [promise1, promise2, ..., promiseN];

RSVP.all을 호출하면 병렬로 실행됩니다.

RSVP.all(promises).then(...); 

그러나 어떻게 순서대로 실행할 수 있습니까?

이렇게 수동으로 쌓을 수 있습니다

RSVP.resolve()
    .then(promise1)
    .then(promise2)
    ...
    .then(promiseN)
    .then(...);

그러나 문제는 promise의 수가 다양하고 promise의 배열이 동적으로 구축된다는 것입니다.


내 다른 답변과 반대 투표에서 더 많은 사람들이 rsvp README 를 읽어야 할 것 같습니다 . "첫 번째 핸들러로부터 약속을 반환 할 때 정말 멋진 부분이 나옵니다." 이렇게하지 않으면 약속의 표현력을 놓치고있는 것입니다.
Michael Johnston

비슷한 질문이지만 프레임 워크 특정은 아닙니다 : stackoverflow.com/q/24586110/245966
jakub.g

답변:


136

이미 배열에 있으면 이미 실행 중입니다. 약속이 있으면 이미 실행 중입니다. 이것은 약속의 문제가 아닙니다 (IE는 메서드 Task와 관련하여 C #과 같지 않습니다 .Start()). .all아무것도 실행하지 않고 약속을 반환합니다.

약속을 반환하는 함수의 배열이있는 경우 :

var tasks = [fn1, fn2, fn3...];

tasks.reduce(function(cur, next) {
    return cur.then(next);
}, RSVP.resolve()).then(function() {
    //all executed
});

또는 값 :

var idsToDelete = [1,2,3];

idsToDelete.reduce(function(cur, next) {
    return cur.then(function() {
        return http.post("/delete.php?id=" + next);
    });
}, RSVP.resolve()).then(function() {
    //all executed
});

3
이것은 논쟁을 필요로하지 않는 동종 약속의 트리를 구성하는 훌륭한 방법입니다. 이는 next_promise 포인터를 사용하여 트리를 직접 구축하는 것과 정확히 동일합니다. 약속 세트가 인수 등과 관련하여 동종이 아닌 경우 수행해야하는 작업입니다. 감소 함수가 현재 포인터를 수행하는 것입니다. -당신을 위해 잎 조금. 당신의 일 중 일부가 동시에 일어날 수 있다면 당신 자신의 나무를 만들고 싶을 것입니다. 약속 트리에서 가지는 시퀀스이고 잎은 동시입니다.
Michael Johnston

답변 주셔서 감사합니다. 약속을 만드는 것은 이미 실행 중임을 의미하므로 내 질문이 올바르게 형성되지 않았습니다. 나는 약속없이 내 문제를 다르게 해결했습니다.
jaaksarv 2013

1
@SSHThis 글쎄, 우선, 와트. 둘째, 이전 응답이 전달됩니다 .then... 그것이 바로이 무시이 예에서
Esailija

3
이 약속 중 하나가 실패하면 ... 오류가 거부되지 않을 것이며, 약속은 해결하지 않습니다
Maxwelll

5
이미 배열에 있으면 이미 실행 중입니다. -이 문구는 굵은 체 + 더 큰 글꼴이어야합니다. 이해하는 것이 중요합니다.
ducin 2017-06-04

22

ECMAScript 2017 비동기 함수를 사용하면 다음과 같이 수행됩니다.

async function executeSequentially() {
    const tasks = [fn1, fn2, fn3]

    for (const fn of tasks) {
        await fn()
    }
}

이제 BabelJS 를 사용하여 비동기 함수를 사용할 수 있습니다.


이것이 현재 (2020) 기본 접근 방식이어야합니다. 처음 사용자의 경우 여기서 두 가지 사항에 유의하는 것이 중요 할 수 있습니다. 1. 약속이 존재하면 이미 진행 중입니다. 따라서 2. fn1, fn2, fn3여기 () => yourFunctionReturningAPromise()에 단지 yourFunctionReturningAPromise(). 이것은 또한 await fn()단지 필요한 이유이기도합니다 await fn. 공식 문서에서 자세한 내용 참조하십시오 . 죄송 주석으로 게시하지만 편집 큐의 전체입니다 :)
ezmegy는

7

2017 년 ES7 방식.

  <script>
  var funcs = [
    _ => new Promise(resolve => setTimeout(_ => resolve("1"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("2"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("3"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("4"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("5"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("6"), 1000)),
    _ => new Promise(resolve => setTimeout(_ => resolve("7"), 1000))
  ];
  async function runPromisesInSequence(promises) {
    for (let promise of promises) {
      console.log(await promise());
    }
  }
  </script>
  <button onClick="runPromisesInSequence(funcs)">Do the thing</button>

이것은 주어진 함수를 병렬이 아닌 순차적으로 (하나씩) 실행합니다. 매개 변수 promises는를 반환하는 함수 배열입니다 Promise.

위 코드를 사용한 Plunker 예제 : http://plnkr.co/edit/UP0rhD?p=preview


4

내가 더 설명하려고 노력하는 대답에 대한 두 번째 시도 :

먼저, RSVP README의 몇 가지 필수 배경 :

정말 멋진 부분은 첫 번째 핸들러에서 프라 미스를 반환 할 때 나옵니다. 이렇게하면 중첩 된 콜백을 평평하게 만들 수 있으며 많은 비동기 코드가있는 프로그램에서 "오른쪽 드리프트"를 방지하는 프라 미스의 주요 기능입니다.

이것이 바로 then그 전에 끝나야 할 약속에서 나중 약속을 반환함으로써 순차적으로 약속을 만드는 방법 입니다.

이러한 일련의 약속을 트리로 생각하면 분기가 순차적 프로세스를 나타내고 잎이 동시 프로세스를 나타내는 트리라고 생각하면 도움이됩니다.

이러한 약속 트리를 구축하는 프로세스는 다른 종류의 트리를 구축하는 매우 일반적인 작업과 유사합니다. 트리에서 현재 분기를 추가하는 위치에 대한 포인터 또는 참조를 유지하고 반복적으로 항목을 추가합니다.

@Esailija가 그의 대답에서 지적했듯이 인수를 사용하지 않는 약속 반환 함수 배열이 있으면 reduce트리를 깔끔하게 구축하는 데 사용할 수 있습니다. reduce를 직접 구현 한 적이 있다면 @Esailija의 답변에서 reduce가 수행하는 작업은 현재 약속 ( cur)에 대한 참조를 유지하고 각 약속이 then.

함수를 반환하는 것을 약속하는 동종의 좋은 배열이없는 경우 또는 단순한 선형 시퀀스보다 더 복잡한 구조가 필요한 경우 다음을 유지하여 약속 트리를 직접 구성 할 수 있습니다. 새 약속을 추가하려는 약속 트리의 위치에 대한 참조 :

var root_promise = current_promise = Ember.Deferred.create(); 
// you can also just use your first real promise as the root; the advantage of  
// using an empty one is in the case where the process of BUILDING your tree of 
// promises is also asynchronous and you need to make sure it is built first 
// before starting it

current_promise = current_promise.then(function(){
  return // ...something that returns a promise...;
});

current_promise = current_promise.then(function(){
  return // ...something that returns a promise...;
});

// etc.

root_promise.resolve();

RSVP.all을 사용하여 약속 "분기"에 여러 "휴가"를 추가하여 동시 및 순차 프로세스의 조합을 작성할 수 있습니다. 너무 복잡하기 때문에 내 반대 투표가 그 예를 보여줍니다.

Ember.run.scheduleOnce ( 'afterRender')를 사용하여 다음 약속이 실행되기 전에 하나의 약속에서 수행 된 작업이 렌더링되도록 할 수 있습니다. 내가 너무 복잡하기 때문에 반대표를 던진 답변도 그 예를 보여줍니다.


3
이것은 훨씬 낫지 만 여전히 주제에서 벗어나고있는 것 같습니다. 이것은 약속에 대한 많은 답변에 공통적이며, 사람들은 질문을 읽는 데 시간을 들이지 않는 것 같으며, 대신 개인적으로 이해하는 약속의 일부 측면에 대해 간단히 언급합니다. 원래 질문은 병렬 실행을 포함하지 않고 조금도 포함하지 않으며 단순히 비아를 연결하는 then것이 바람직 하다는 것을 명확하게 보여줍니다 . 질문에 대한 답변을 숨기는 많은 추가 정보를 제공했습니다.
David McMullin 2013

@DavidMcMullin ".... 그리고 그것은 단순히 then을 통한 연결이 바람직하다는 것을 분명히 보여줍니다 ..."그러나 실제로 그는 약속의 순서가 동적으로 구축된다고 말합니다. 따라서 그는 트리를 구성하는 방법을 이해할 필요가 있습니다.이 경우 트리 "선형 시퀀스"의 단순한 하위 집합이더라도 말입니다. 체인의 마지막 약속에 대한 참조를 유지하고 새로운 약속을 추가하여이를 구축해야합니다.
Michael Johnston

OP가 "프로 미스의 수는 다양하고 프라 미스의 배열은 동적으로 구축된다"고 말했을 때, 나는 모든 s / 그가 의미하는 것은 배열의 크기가 미리 결정되지 않았기 때문에 간단한 것을 사용할 수 없다는 것을 확신합니다. Promise.resolve().then(...).then(...)..., 약속이 실행 되는 동안 어레이가 커지는 것은 아닙니다 . 물론 지금은 모두 문제입니다.
JLRishe

4

또 다른 접근 방식은 프로토 타입 에 전역 시퀀스 함수 를 정의하는 것 Promise입니다.

Promise.prototype.sequence = async (promiseFns) => {
  for (let promiseFn of promiseFns) {
    await promiseFn();
  }
}

그런 다음 어디서나 사용할 수 있습니다. Promise.all()

const timeout = async ms => new Promise(resolve =>
  setTimeout(() => {
    console.log("done", ms);
    resolve();
  }, ms)
);

// Executed one after the other
await Promise.sequence([() => timeout(1000), () => timeout(500)]);
// done: 1000
// done: 500

// Executed in parallel
await Promise.all([timeout(1000), timeout(500)]);
// done: 500
// done: 1000

면책 조항 : 프로토 타입을 신중하게 편집하십시오!


2

for루프 를 해결하려면 모든 것이 필요합니다. :)

var promises = [a,b,c];
var chain;

for(let i in promises){
  if(chain) chain = chain.then(promises[i]);
  if(!chain) chain = promises[i]();
}

function a(){
  return new Promise((resolve)=>{
    setTimeout(function(){
      console.log('resolve A');
      resolve();
    },1000);
  });
}
function b(){
  return new Promise((resolve)=>{
    setTimeout(function(){
      console.log('resolve B');
      resolve();
    },500);
  });
}
function c(){
  return new Promise((resolve)=>{
    setTimeout(function(){
      console.log('resolve C');
      resolve();
    },100);
  });
}

이유는 무엇입니까 if(!chain) chain = promises[i]();()말을? 나는 체인이 비어있는 경우 (반복 0) 원시 약속을 원할 것이고 루프는 각 후속 약속을 체인의 .then(). 따라서 이것은 if(!chain) chain = promises[i];아닐까요? 아마도 나는 여기서 뭔가를 이해하지 못했을 것입니다.
Halfer

아-당신 a,b,c은 실제로 약속이 아니라 약속을 반환하는 함수입니다. 따라서 위의 내용이 의미가 있습니다. 그러나 이런 방식으로 약속을 포장하는 데 어떤 유용성이 있습니까?
Halfer

2

비슷한 문제가 있었고 순차적으로 함수를 하나씩 실행하는 재귀 함수를 만들었습니다.

var tasks = [fn1, fn2, fn3];

var executeSequentially = function(tasks) {
  if (tasks && tasks.length > 0) {
    var task = tasks.shift();

    return task().then(function() {
      return executeSequentially(tasks);
    });
  }

  return Promise.resolve();  
};

다음 함수에서 출력을 수집해야하는 경우 :

var tasks = [fn1, fn2, fn3];

var executeSequentially = function(tasks) {
  if (tasks && tasks.length > 0) {
    var task = tasks.shift();

    return task().then(function(output) {
      return executeSequentially(tasks).then(function(outputs) {
        outputs.push(output);

        return Promise.resolve(outputs);  
      });
    });
  }

  return Promise.resolve([]);
};

0
export type PromiseFn = () => Promise<any>;

export class PromiseSequence {
  private fns: PromiseFn[] = [];

  push(fn: PromiseFn) {
    this.fns.push(fn)
  }

  async run() {
    for (const fn of this.fns) {
      await fn();
    }
  }
}

그때

const seq = new PromiseSequence();
seq.push(() => Promise.resolve(1));
seq.push(() => Promise.resolve(2));
seq.run();

프라 미스가 반환하는 것을 다른 개인 변수에 저장하고 콜백에 전달할 수도 있습니다.


-1

내가 추구했던 것은 본질적으로 mapSeries 였고, 일련의 값에 대한 매핑 저장을 수행하고 결과를 원합니다.

그래서, 여기에 내가 가진 한, 다른 사람들이 미래에 비슷한 것을 찾도록 도울 수 있습니다 ..

(컨텍스트는 Ember 앱입니다).

App = Ember.Application.create();

App.Router.map(function () {
    // put your routes here
});

App.IndexRoute = Ember.Route.extend({
    model: function () {
            var block1 = Em.Object.create({save: function() {
                return Em.RSVP.resolve("hello");
            }});
    var block2 = Em.Object.create({save: function() {
            return Em.RSVP.resolve("this");
        }});
    var block3 = Em.Object.create({save: function() {
        return Em.RSVP.resolve("is in sequence");
    }});

    var values = [block1, block2, block3];

    // want to sequentially iterate over each, use reduce, build an array of results similarly to map...

    var x = values.reduce(function(memo, current) {
        var last;
        if(memo.length < 1) {
            last = current.save();
        } else {
            last = memo[memo.length - 1];
        }
        return memo.concat(last.then(function(results) {
            return current.save();
        }));
    }, []);

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