Jak zsynchronizować asynchroniczne wyświetlanie w dwóch pętlach?

0

Mam kod JavaScript, którego minimal working example wygląda (mniej-więcej) tak:

const exampleInputList = [
    input1,
    input2,
    input3
];

const run = function (inputList, processInput) {
    const results = [];

    for (const input of inputList) {
        const result = processInput(input);
        results.push(result);
    }

    return results;
}

const results = run(
    exampleInputList,
    (input) => {
        const environment = initEnvironment();
        while (true) {
            const result = process(input, environment); // Nowy wynik w każdej pętli
            if (isConditionFullfiled(result)) {
                console.log(input.id, "Finished! Result:", result)
                return true;
            }
            setTimeout(() => console.log(input.id, "Current step's result: ", result), 1000);
        }
    }
);

results.forEach(result => logToFile(result));

Wynik wykonania tego kodu (w konsoli) powinien być mniej-więcej taki:

0 Current step's result: // tutaj wynik kroku nr 1 dla wejścia 0
0 Current step's result: // tutaj wynik kroku nr 2 dla wejścia 0
0 Current step's result: // ...
0 Finished! Result: // tutaj wynik całości dla wejścia 0
1 Current step's result: // tutaj wynik kroku nr 1 dla wejścia 1
1 Current step's result: // tutaj wynik kroku nr 2 dla wejścia 1
...

Problemem jest to, żeby każda linijka była wyświetlana co sekundę – po to właśnie umieściłem wywołanie funkcji setTimeout. Jak to osiągnąć – na przykład za pomocą promises?


PS. Dla uproszczenia zakładam w powyższym kodzie, że pętla while zawsze się kończy.


UPDATE: Dla czytelności tego przykładu zmieniłem kod – teraz tablica wejściowa jest podawana explicite.


PS2. Dodałem kod odpowiedzialny za przetwarzanie tablicy results – wcześniej go nie było, więc wyglądało, jakby nie była potrzebna (a póki co jest).


UPDATE2: Dodałem linijki w wyjściu, które lepiej pokazują, że wyjście powinno pokazać się dla wszystkich wejść po kolei.

1

Nie wiem, czy rozumiem do końca przykład/problem, ale chodzi o to, żeby zsynchronizować wyświetlanie napisów co sekundę (zachowując kolejność)?

Może taki pattern się przyda:


function timeout(time) { // funkcja konwertująca setTimeout do formy bardziej promise-friendly
   return () => new Promise(resolve => {
       setTimeout(resolve, time); 
   });
}

let promise = Promise.resolve(); 
...
promise = promise.then(timeout(1000))
   .then(() => console.log(input.id, "Current step's result: ", result));

Chociaż trochę strzelam w ciemno, bo nie rozumiem do końca, jaki problem jest do rozwiązania. Chociaż na podstawie tego, jakie problemy ja miałem z tego typu rzeczami, to ta linijka:
promise = promise.then(....) pozwalała mi na tworzenie takich jakby asynchronicznych łańcuchów.

ale być może próbuję rozwiązać inny problem, niż masz, nie wiem.

Jak zsynchronizować asynchroniczne wyświetlanie w dwóch pętlach?

To brzmi trochę jak use case dla generatorów albo obserwabli, chociaż to zależy. Bo może promisy wystarczą.

0

Dzięki za sugestie.

Nie wiem, czy rozumiem do końca przykład/problem, ale chodzi o to, żeby zsynchronizować wyświetlanie napisów co sekundę (zachowując kolejność)?

Tak, o to chodzi. Najpierw wszystkie napisy, każdy co sekundę, dla pierwszego zestawu danych, potem dla drugiego i tak dalej. Jeśli każdy zestaw danych ma 10 elementów, to dla 3 zestawów danych całość powinna być wyświetlana w czasie około 30 sekund. Napisałem "około", bo wynik dla danego zestawu może być wyświetlany "równo" z pierwszym napisem dla kolejnego zestawu. Tu wszystko mi jedno, czy będzie "równo", czy sekundę po.

Może taki pattern się przyda:
(…)

Powiem Ci, że natrafiłem na ten wzorzec wczoraj (po utworzeniu wątku). Ale jeszcze myślę, jak go zaadoptować. Wydaje się obiecujący.

To brzmi trochę jak use case dla generatorów albo obserwabli (…)

Właśnie skorzystałem z generatorów wczoraj w tym kodzie (ale nie jestem pewien, czy da się to wykorzystanie ująć jako problem rozwiązanie problemu z synchronizacją). Ale to rozwiązało inny mój problem, nie ten, który opisuję w tym wątku.

0

Chyba udało mi się rozwiązać ten problem – w jakiejś części lub w całości. Wykorzystałem ten wzorzec, o którym, @LukeJL, pisałeś wyżej.

Oto kod, zgodnie z minimal working example z pierwszego postu:

const exampleInputList = [
    input1,
    input2,
    input3
];

const run = function (inputList, processInput) {
    const results = [];

    let chain = Promise.resolve();

    for (const input of inputList) {
        results.push(result); // Zapisz poprzedni wynik
        return processInput(input); // Oblicz nowy wynik
    }

    return results;
}

const results = run(
    exampleInputList,
    (input) => {
        const environment = initEnvironment();
        return new Promise(resolve => {
            const setIntervalID = setInterval(() => {
                const result = process(input, environment); // Nowy wynik w każdej pętli
                console.log(input.id, "Current step's result: ", result);
                if (isConditionFullfiled(result)) {
                    clearInterval(setIntervalID);
                    resolve("Finished! Result:" + result);
                }
            }, 1000);
        };
    }
);

results.forEach(result => logToFile(result));

Ze zmian niezwiązanych z problemem wątku: przeniosłem wyświetlanie bieżącego wyniku przed warunek zakończenia. Gdybym tego nie zrobił, po zakończeniu przetwarzania danego wejścia – tak mi się przynajmniej wydaje – najpierw wyświetliłby się wynik, a potem dopiero informacja o bieżącym stanie ostatniego kroku (console.log(input.id, "Current step's result: ", result);).

Jeszcze dopracuję kod, by w całości działał tak, jak chciałem; zobaczę, czy nie ma innych problemów powiązanych z tym, i napiszę.


UPDATE:

Zauważyłem błąd: ostatnia linijka nic nie wyświetla. Żeby odnosiła zamierzony efekt, należy jeszcze zmienić kod.

Teraz jest tak, że zmienna results w momencie wykonania na niej pętli wskazuje na pustą tablicę. Wykonałem 3 zmiany: w kodzie funkcji run, w nazwie zmiennej results oraz w kodzie z pętlą forEach.

Zmieniony kod funkcji run:

const run = function (inputList, processInput) {
    return new Promise(resolve => {
        const results = [];

        for (const input of inputList) {
            const result = processInput(input);
            results.push(result);
        }

        chain.then(() => resolve(results) });
    });
}

Zmieniona nazwa zmiennej results:

const whenRunDone = run(
    ...
);

Zmieniony kod z pętlą forEach:

whenRunDone.then(results => {
    results.forEach(r => {
        console.log(r, "\n");
    })
});

I już obecnie na końcu wszystkich iteracji wszystkie wyniki wyświetlają się.

Widzę jeszcze jeden problem: pierwszy wynik w tablicy jest równy undefined. Będę próbować, by nie był.


UPDATE2: Nie tylko pierwszy wynik jest równy undefined, ale również nie wyświetla się dodaje się do tablicy ostatni wynik. To już gorzej. :/


UPDATE3: Wydaje się, że rozwiązałem już powyższy problem. Tym samym wydaje się, że są rozwiązane wszystkie problemy z kodem. Zrobiłem to zmieniając kod funkcji run w poniższy sposób:

const run = function (inputDataList, processInputDataListElem) {
    return new Promise(resolve => {
        const results = [];

        let chain = Promise.resolve();

        for (const inputData of inputDataList) {
            chain = chain.then(() => {
                return processInput(input)
                    .then(result => new Promise(resolve => {
                        results.push(result);
                        resolve();
                    }));
            });
        }

        chain.then(() => resolve(results));
    });
}

Obecnie zarówno pierwszy element tablicy z wynikami nie jest już undefined, jak i ostatni wynik jest do niej dodawany.

1 użytkowników online, w tym zalogowanych: 0, gości: 1