Jak mogę wykonać szereg obietnic w kolejności sekwencyjnej?

81

Mam szereg obietnic, które muszą być uruchamiane w kolejności.

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

Wywołanie RSVP.all spowoduje ich równoległe wykonanie:

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

Ale jak mogę je uruchomić po kolei?

Mogę ręcznie układać je w ten sposób

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

ale problem polega na tym, że liczba obietnic jest różna, a szereg obietnic jest budowany dynamicznie.

jaaksarv
źródło
z innych odpowiedzi i głosów przeciw moim, wydaje się, że więcej osób powinno przeczytać plik README rsvp, w którym wyjaśnia on: „Naprawdę niesamowita część pojawia się, gdy zwracasz obietnicę od pierwszego handlera”. Jeśli tego nie robisz, naprawdę tracisz wyrazistą moc obietnic.
Michael Johnston,
Podobne pytanie, ale niezwiązane z platformą: stackoverflow.com/q/24586110/245966
jakub.g

Odpowiedzi:

136

Jeśli masz już je w tablicy, to już są wykonywane. Jeśli masz obietnicę, to jest ona już wykonywana. Nie dotyczy to obietnic (tj. Nie są one podobne do C # Taskpod względem .Start()metody). .allnie wykonuje niczego, po prostu zwraca obietnicę.

Jeśli masz tablicę funkcji zwracających obietnice:

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

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

Lub wartości:

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
});
Esailija
źródło
3
to doskonały sposób na skonstruowanie drzewa jednorodnych obietnic, które nie wymagają argumentów. Jest to dokładnie równoważne użyciu wskaźnika next_promise do samodzielnego zbudowania drzewa, co musisz zrobić, jeśli zestaw obietnic nie jest jednorodny pod względem argumentów itp. Po prostu funkcja redukuj wykonuje wskaźnik na bieżący - kawałek dla ciebie. Będziesz także chciał zbudować swoje drzewo, jeśli niektóre z twoich rzeczy mogą się wydarzyć jednocześnie. W drzewie obietnic gałęzie są sekwencjami, a liście są zbieżne.
Michael Johnston
Dziękuję za Twoją odpowiedź. Masz rację, że tworzenie obietnicy już oznacza, że ​​jest ona wykonywana, więc moje pytanie nie zostało poprawnie sformułowane. Skończyło się na tym, że rozwiązałem swój problem inaczej, bez obietnic.
jaaksarv
1
@SSHThis Po pierwsze, wat. Po drugie, poprzednia odpowiedź jest przekazywana .then, w tym przykładzie jest po prostu ignorowana ...
Esailija
3
Jeśli którakolwiek z tych obietnic zawiedzie, błąd nigdy nie zostanie odrzucony, a obietnica nigdy nie zostanie rozwiązana ...
Maxwelll
5
Jeśli masz już je w tablicy, to już są wykonywane. - ta fraza powinna być pogrubiona + większa czcionka. Ważne jest, aby zrozumieć.
ducin
22

W przypadku funkcji asynchronicznych ECMAScript 2017 powinno wyglądać to następująco:

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

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

Możesz teraz używać BabelJS do korzystania z funkcji asynchronicznych

ujeenator
źródło
To powinno być obecnie podejściem domyślnym (2020). Dla początkujących użytkowników może być ważne, aby zwrócić tutaj uwagę na dwie rzeczy: 1. Kiedy obietnica istnieje, to już działa. Dlatego bardzo ważne jest, aby 2. fn1, fn2, fn3tutaj były funkcje, np () => yourFunctionReturningAPromise(). W przeciwieństwie do just yourFunctionReturningAPromise(). Jest to również powód, dla którego await fn()jest to konieczne zamiast tego po prostu await fn. Zobacz więcej w oficjalnych dokumentach . Przepraszamy za komentarz, ale kolejka edycji jest pełna :)
ezmegy
7

Droga ES7 w 2017 roku.

  <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>

Spowoduje to wykonanie podanych funkcji sekwencyjnie (jedna po drugiej), a nie równolegle. Parametr promisesjest tablicą funkcji, które zwracają Promise.

Przykład Plunkera z powyższym kodem: http://plnkr.co/edit/UP0rhD?p=preview

allenhwkim
źródło
4

Druga próba odpowiedzi, w której staram się wyjaśnić:

Po pierwsze, niezbędne informacje z pliku README RSVP :

Naprawdę niesamowita część pojawia się, gdy zwracasz obietnicę z pierwszego modułu obsługi ... Pozwala to na spłaszczenie zagnieżdżonych wywołań zwrotnych i jest główną cechą obietnic, która zapobiega „dryfowaniu w prawo” w programach z dużą ilością kodu asynchronicznego.

Dokładnie w ten sposób składasz obietnice sekwencyjnie, zwracając późniejszą obietnicę z thenobietnicy, która powinna się zakończyć przed nią.

Warto pomyśleć o takim zestawie obietnic jak o drzewie, w którym gałęzie reprezentują procesy sekwencyjne, a liście - procesy współbieżne.

Proces tworzenia takiego drzewa obietnic jest analogiczny do bardzo częstego zadania budowania innych rodzajów drzew: utrzymuj wskaźnik lub odniesienie do miejsca w drzewie, w którym obecnie dodajesz gałęzie, i iteracyjnie dodawaj elementy.

Jak zauważył @Esailija w swojej odpowiedzi, jeśli masz tablicę funkcji zwracających obietnice, które nie przyjmują argumentów, możesz użyć, reduceaby starannie zbudować drzewo. Jeśli kiedykolwiek wdrożyłeś redukcję dla siebie, zrozumiesz, że to, co redukuje robi za kulisami w odpowiedzi @ Esailija, to utrzymywanie odniesienia do bieżącej obietnicy ( cur) i zwracanie każdej obietnicy następnejthen .

Jeśli NIE masz ładnej tablicy jednorodnych (w odniesieniu do argumentów, które przyjmują / zwracają) obietnic zwracających funkcje lub jeśli potrzebujesz bardziej skomplikowanej struktury niż prosta sekwencja liniowa, możesz samodzielnie skonstruować drzewo obietnic, zachowując odniesienie do pozycji w drzewie obietnic, w której chcesz dodać nowe obietnice:

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();

Można tworzyć kombinacje procesów współbieżnych i sekwencyjnych, używając RSVP.all do dodawania wielu „liści” do „gałęzi” obietnicy. Moja odpowiedź, na którą przyznaję, że jest zbyt skomplikowana, jest tego przykładem.

Możesz również użyć Ember.run.scheduleOnce ('afterRender'), aby upewnić się, że coś, co zostało zrobione w jednej obietnicy, zostanie renderowane przed uruchomieniem następnej obietnicy - moja odpowiedź, która nie została uznana za zbyt skomplikowaną, również pokazuje tego przykład.

Michael Johnston
źródło
3
To jest o wiele lepsze, ale wydaje mi się, że wciąż odchodzisz od tematu. Jest to typowe dla wielu odpowiedzi dotyczących obietnic, ludzie nie poświęcają czasu na przeczytanie pytania, zamiast tego po prostu komentują jakiś aspekt obietnic, które osobiście rozumieją. Oryginalne pytanie nie wymaga równoległego wykonania, ani trochę, i wyraźnie pokazuje, że po prostu łańcuch za pośrednictwem thenjest pożądany, podałeś wiele dodatkowych informacji, które ukrywają odpowiedź na zadane pytanie.
David McMullin,
@DavidMcMullin ".... i to wyraźnie pokazuje, że proste połączenie przez to jest pożądane ...", ale w rzeczywistości stwierdza, że ​​sekwencja obietnic jest budowana dynamicznie. Musi więc zrozumieć, jak skonstruować drzewo, nawet jeśli w tym przypadku jest to prosty podzbiór „ciągu liniowego” drzewa. Nadal musisz go zbudować, zachowując odniesienie do ostatniej obietnicy w łańcuchu i dodając do niej nowe obietnice.
Michael Johnston,
Kiedy OP powiedział, że „liczba obietnic jest różna, a tablica obietnic jest budowana dynamicznie”, jestem prawie pewien, że miał na myśli tylko to, że rozmiar tablicy nie był z góry określony i dlatego nie mógł użyć prostego Promise.resolve().then(...).then(...)..., nie żeby tablica rosła podczas wykonywania obietnic. Oczywiście teraz wszystko jest dyskusyjne.
JLRishe
4

Jeszcze innym podejściem jest zdefiniowanie funkcji sekwencji globalnej w Promiseprototypie.

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

Wtedy możesz go używać wszędzie, tak jak Promise.all()

Przykład

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

Zastrzeżenie: zachowaj ostrożność podczas edycji prototypów!

Ben Winding
źródło
2

Do rozwiązania tego wszystkiego potrzeba forpętli :)

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);
  });
}
Paweł
źródło
Dlaczego if(!chain) chain = promises[i]();ma ()na końcu? Myślę, że w przypadku, gdy łańcuch jest pusty (iteracja 0), chciałoby się po prostu mieć surową obietnicę, a następnie pętla może wstrzyknąć każdą kolejną obietnicę do łańcucha .then(). Tak więc nie byłoby to możliwe if(!chain) chain = promises[i];? Być może czegoś tu nie zrozumiałem.
halfer
Ach - a,b,cfaktycznie są to funkcje zwracające obietnice, a nie obietnice. Więc powyższe ma sens. Ale jaka jest użyteczność zawijania obietnic w ten sposób?
halfer
2

Miałem podobny problem i utworzyłem funkcję rekurencyjną, która uruchamia funkcje po kolei.

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();  
};

Jeśli chcesz zebrać dane wyjściowe z tych funkcji:

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([]);
};
mrded
źródło
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();
    }
  }
}

następnie

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

możliwe jest również przechowywanie tego, co obiecuje zwrot w innej prywatnej zmiennej i przekazywanie jej do wywołań zwrotnych

Daniel Khoroshko
źródło
-1

To, czego szukałem, to zasadniczo mapSeries i tak się składa, że ​​mapuję zapisywanie zestawu wartości i chcę wyników.

Tak więc, tak daleko, jak mam, aby pomóc innym w szukaniu podobnych rzeczy w przyszłości.

(Zwróć uwagę, że kontekstem jest aplikacja 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);
    }
});
Julian Leviston
źródło