Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Задача на Event Loop (https://javascript.ru/forum/misc/83137-zadacha-na-event-loop.html)

Himmelin 27.09.2021 14:45

Задача на Event Loop
 
Добрый день! Столкнулся с интересной задачей и не знаю как решить.

function one(callback) {
    setTimeout( function() {
        callback("First");
    }, Math.random() * 100);
}

function two(callback) {
    setTimeout( function() {
        callback("Second");
    }, Math.random() * 100);
}

function three(callback) {
    setTimeout( function() {
        callback("Third");
    }, Math.random() * 100);
}

function runCallback(s) {
    console.log(s);
}


Нужно вызвать функции one, two и three в правильной последовательности, чтобы в консоль вывелось A, B и C в нужном порядке. При этом использовать можно все, даже дописывать runCallback, но сами функции менять нельзя. :help:

Aetae 27.09.2021 15:35

Эту задачу можно понимать по разному.
В простейшем варианте вы вызываете следующую функцию из runCallback когда закончилась предыдущая.

Но можно и такое изобразить:
function stream(log, ...args) {
  let index = 0;
  const results = new Map();
  const callback = (i, result) => {
    if(i === index) {
      while(true) {
        log(result);
        if(!results.has(++index))
          break;
        result = results.get(index);
        results.delete(index);
      }
    } else {
      results.set(i, result);
    }
  };
  
  args.forEach((func, i) => func(result => callback(i, result)));
}

stream(runCallback, one, two, three)
получив максимальную возможную скорость вызовов.

Himmelin 27.09.2021 15:44

Понял, спасибо! :)

ksa 27.09.2021 20:43

Предложу решение "в лоб"...

<!DOCTYPE html>
<html>
<head>
<!--
<script src='https://code.jquery.com/jquery-latest.min.js'></script>
-->
<style type='text/css'>
</style>
<script>
function one(callback) {
    setTimeout( function() {
        callback("First");
    }, Math.random() * 2000);
}

function two(callback) {
    setTimeout( function() {
        callback("Second");
    }, Math.random() * 2000);
}

function three(callback) {
    setTimeout( function() {
        callback("Third");
    }, Math.random() * 2000);
}

function runCallback(s) {
    const o = document.createElement('p')
	o.textContent = s
	document.querySelector('body').appendChild(o)
}

// Вот собственно решение...
const test1 = res => {
	runCallback(res)
	three(runCallback)
}
const test2 = res => {
	runCallback(res)
	two(test1)
}
one(test2)

</script>
</head>
<body> 

</body>
</html>

ksa 27.09.2021 20:48

Или вовсе так...
<!DOCTYPE html>
<html>
<head>
<!--
<script src='https://code.jquery.com/jquery-latest.min.js'></script>
-->
<style type='text/css'>
</style>
<script>
function one(callback) {
    setTimeout( function() {
        callback("First");
    }, Math.random() * 2000);
}

function two(callback) {
    setTimeout( function() {
        callback("Second");
    }, Math.random() * 2000);
}

function three(callback) {
    setTimeout( function() {
        callback("Third");
    }, Math.random() * 2000);
}

function runCallback(s) {
    const o = document.createElement('p')
	o.textContent = s
	document.querySelector('body').appendChild(o)
}

// Вот собственно решение...
one(res => {
	runCallback(res)
	two(res => {
		runCallback(res)
		three(runCallback)
	})
})

</script>
</head>
<body> 
</body>
</html>

voraa 27.09.2021 21:34

Я считаю предыдущие решения некорректными, т.к. при вызове функций используется другой callback. Не сам runCallback
Предлагаю свое решение
function one(callback) {
    setTimeout( function() {
        callback("First");
    }, Math.random() * 1000);
}
 
function two(callback) {
    setTimeout( function() {
        callback("Second");
    }, Math.random() * 1000);
}
 
function three(callback) {
    setTimeout( function() {
        callback("Third");
    }, Math.random() * 1000);
}
 
function runCallback(s) {
    console.log(s);
}

let console;
function call (...funs) {
    const cl = window.console.log
    let i = 0
    console = {
        log (...args) {
            cl(...args)
            if (i<funs.length) 
                 funs[i++](runCallback)
             else
                 console = window.console;
        }   
    }
    funs[i++](runCallback)
}

call(one, two, three)

Alexandroppolus 27.09.2021 22:21

Цитата:

Сообщение от voraa
Я считаю предыдущие решения некорректными, т.к. при вызове функций используется другой callback. Не сам runCallback

то есть вместо патча runCallback сделать патч console.log - это будет честнее? :D

имхо, раз уж runCallback и callback у функций не склеены изначально, то между ними и надо втыкать "мидлвар".

А в целом задача решается в одну строку, если не городить велосипеды:
// ----- условие --------------
function one(callback) {
    setTimeout( function() {
        callback("First");
    }, Math.random() * 100);
}

function two(callback) {
    setTimeout( function() {
        callback("Second");
    }, Math.random() * 100);
}

function three(callback) {
    setTimeout( function() {
        callback("Third");
    }, Math.random() * 100);
}

function runCallback(s) {
    console.log(s);
}

// ----- решение --------------
Promise.all([one, two, three].map(f => new Promise(f))).then(r => r.forEach(runCallback));

Aetae 28.09.2021 02:07

Alexandroppolus, но вся суть в велосипедах.)
Твоя версия запускает все функции сразу и разом выводит всё когда получены все результаты.
Время первого вывода на экран = Время выполнения самой медленной функции.
Время последнего вывода на экран = Время выполнения самой медленной функции.

Версии voraa и ksa запускают функции и выводят результат по порядку.
Время первого вывода на экран = Время выполнения первой функции.
Время последнего вывода на экран = Сумма времени выполнения всех функций.

Мой велосипед запускает все функции сразу, но выводит по мере исполнения и по порядку.)
Время первого вывода на экран = Время выполнения первой функции.
Время последнего вывода на экран = Время выполнения самой медленной функции.

В худшем случае: если первая функция - самая медленная, то результат будет как у тебя, но это крайний вариант. Примерно так работают async стримы, только там есть ещё ограничение на количество одновременно запущенных потоков, чтоб все ресурсы не съело.)

Alexandroppolus 28.09.2021 09:09

Aetae, вариант со стримом прикольный, жизненный. Возможно, так и было в исходном условии, но Himmelin сыграл в испорченный телефон.

рони 28.09.2021 09:40

Aetae,
ksa,
voraa,
Alexandroppolus,
:thanks:

Vlasenko Fedor 28.09.2021 11:28

(function test(queue) {
    const fn = queue.shift()
    fn(s => {
        runCallback(s)
        if (queue.length) test(queue)
    })
})([one, two, three])

пример с рекурсией :)

voraa 28.09.2021 11:46

Ну если воспользоваться идеями Aetae и Alexandroppolus и попытаться объединить их, то получается такой вариант
function one(callback) {
    setTimeout( function() {
        callback("First");
    }, Math.random() * 1000);
}
 
function two(callback) {
    setTimeout( function() {
        callback("Second");
    }, Math.random() * 500);
}
 
function three(callback) {
    setTimeout( function() {
        callback("Third");
    }, Math.random() * 100);
}
 
function runCallback(s) {
    console.log(s);
}

async function stream(log, ...args) {
    const ap = args.map (func => new Promise(func))
    for (let i = 0; i < args.length; i++) {
        log(await ap[i])       
    }
}
 
stream(runCallback, one, two, three)

Vlasenko Fedor 28.09.2021 11:55

Тогда так на колбеках :)
function one(callback) {
    setTimeout( function() {
        callback("First");
    }, Math.random() * 1000);
}
 
function two(callback) {
    setTimeout( function() {
        callback("Second");
    }, Math.random() * 500);
}
 
function three(callback) {
    setTimeout( function() {
        callback("Third");
    }, Math.random() * 100);
}
 
function runCallback(s) {
    console.log(s);
}

const stream = (log, ...queue) => queue.shift()(s => (log(s), queue.length && stream(log, ...queue)))
stream(runCallback, one, two, three)

ksa 28.09.2021 12:07

Цитата:

Сообщение от Aetae
Мой велосипед запускает все функции сразу, но выводит по мере исполнения и по порядку.)

Конечно это более навороченный вариант.

ksa 28.09.2021 12:08

Цитата:

Сообщение от voraa
Я считаю предыдущие решения некорректными, т.к. при вызове функций используется другой callback. Не сам runCallback

Так в задании как сказано
Цитата:

Сообщение от Himmelin
При этом использовать можно все, даже дописывать runCallback, но сами функции менять нельзя.

Т.ч. ничего в условии задачи не нарушено. :no:

Aetae 28.09.2021 13:06

voraa, во, вот это мило.)
Ток можно вот так вот модняво записать, чтоб ваще:
async function stream(log, ...args) {
  for await(let result of args.map(func => new Promise(func))) {
    log(result)       
  }
}

Жаль в js нельзя как в жабе сделать так: args.map(Promise::new), тогдаб вообще идеально было.)

Alexandroppolus 28.09.2021 13:14

Цитата:

Сообщение от Aetae
Жаль в js нельзя как в жабе сделать так: args.map(Promise::new)

некоторые конструкторы умеют без new (например, Object, Function, RegExp), но промис почему-то нет.

Alexandroppolus 28.09.2021 13:23

тот же вариант без асинк/авайт:
function stream(log, ...args) {
    args.reduce((acc, func) => {
        const prom = new Promise(func);
        return acc.then(() => prom).then(log);
    }, Promise.resolve());
}

Aetae 28.09.2021 13:33

Alexandroppolus, да обычный расколабас. Те же классы которые через class не могут работать без new по спеке.
Всегда можно сделать хрень типа:
window.Promise = new Proxy(Promise, {
  apply (target, _, args) {
    return new target(...args);
  }
});
но как-то оно...

Vlasenko Fedor 28.09.2021 13:36

Пошла битва за буковки :haha:
const stream = (log, ...args) => args.reduce((a, f) => a.then(_ => new Promise(f)).then(log), Promise.resolve());
const stream = (log, ...args) => args.shift()(s => (log(s), args.length && stream(log, ...args)))

Alexandroppolus 28.09.2021 13:56

Vlasenko Fedor,
у тебя последовательный вызов функций, не "стрим" в терминологии этого топика

Vlasenko Fedor 28.09.2021 14:01

Alexandroppolus,

Цитата:

Сообщение от Alexandroppolus
у тебя последовательный вызов функций, не "стрим" в терминологии этого топика

Цитата:

Сообщение от Himmelin
Нужно вызвать функции one, two и three в правильной последовательности, чтобы в консоль вывелось A, B и C в нужном порядке

Тогда это очередь, а не стрим :lol:
const q = (l, ...a) => a.shift()(s => (l(s), a[0] && q(l, ...a)))
q(runCallback, one, two, three)

Himmelin 28.09.2021 14:58

Вот к этому решению я пытался прийти, знал что через промисы как-то можно, но чет не пришел(

voraa 28.09.2021 15:08

Цитата:

Сообщение от Vlasenko Fedor
Тогда это очередь, а не стрим

Это как сказать.
Вот возьмем реальный стрим видео по http.
Сервер пакеты посылает в правильной последовательности.
Показывать их тоже надо в правильной последовательности.
А вот приходят они иногда не всегда в правильной. И если есть возможность, то можно подождать задержавшийся пакет, особенно, если это опорный кадр


Часовой пояс GMT +3, время: 02:16.