Итак, я пытался обдумать возможное решение, но мои попытки привести простой пример не увенчались успехом.
Итак, у меня есть массив данных, который выглядит примерно так:
const arr = [{
"content": "FIRST_CONTENT_HERE",
"callback": function callback () {}
},
{
"content": "SECOND_CONTENT_HERE",
"callback": function callback () {}
},
{
"content": "THIRD_CONTENT_HERE",
"callback": function callback () {}
},
{
"content": "THIRD_CONTENT_HERE",
"callback": function callback () {}
}
];
Я хочу создать генератор для итерации по этому массиву, но не возвращать (выход?) управление обратно к следующей итерации, пока эта функция обратного вызова не будет выполнена. Я попытался обернуть этот обратный вызов в обещание и сделал так, чтобы обещание не разрешилось, пока этот обратный вызов не будет выполнен. Я попытался убедиться, что обратный вызов был асинхронной функцией, чтобы обернуть это обещание. Я пробовал несколько вещей, но я просто не смог заставить их работать. После того, как я вызвал функцию генератора и перебрал ее, она просто перебрала каждый элемент массива. Независимо от того, как я настроил цикл, он никогда не ждал выполнения обратного вызова, прежде чем перейти к следующей итерации.
Я искал, но в большинстве найденных примеров генераторов и того, как они взаимодействовали с промисами (или асинхронными функциями), примеры всегда показывали что-то вроде этого yield Promise.resolve(x)
, а это не то, что мне нужно. Я не хочу, чтобы он разрешался сразу — мне нужно, чтобы он дождался выполнения callback
. И обратный вызов будет настроен как обработчик onclick (для закрытия диалога) и будет выполняться только тогда, когда это событие произойдет. Таким образом, следующий диалог не должен создаваться до тех пор, пока не будет закрыт предыдущий. Итак, для каждого цикла массива происходит несколько вещей:
callback()
должен быть выполнен где-то там.Я не говорю, что мой подход правильный. Я мог быть полностью и полностью не в своей тарелке. Но что-то крутится у меня в голове, что генераторы и промисы — это то, что нужно.
Как мне лучше всего реализовать функциональность, которую я пытаюсь достичь? Как я уже сказал, я просто не могу уложить это в голове...
Вот пример кода, который я написал. Это претерпело несколько изменений, поскольку я пробовал разные вещи. Я использую здесь fetch(), потому что знаю, что она возвращает обещание. Я убежден, что обещания должны быть где-то в моем решении, чтобы убедиться, что мой неасинхронный callback()
выполняется до того, как управление перейдет. Это глупо, но я просто пробовал разные доказательства концепции.
async function runRequest(url) {
const response = await fetch(url);
return response;
}
async function* setupGenerator(arr) {
for (let i = 0; i < arr.length; i++) {
const response = await runRequest(arr[i]);
yield response;
}
}
const urls = [
'/activityViewer/index.html',
'/message/index.html',
'/users/index.html',
];
debugger;
const generator = setupGenerator(urls);
for (let response of generator) {
console.info('In FOR loop; response; ', response);
}
спасибо,
Кристоф
эти функции возвращают обещания или принимают аргументы обратного вызова? потому что, если нет, JavaScript является однопоточным, поэтому вы не можете «уступить контроль», пока вызываемая функция не вернется или не получит неявный возврат, после чего функция будет выполнена, и вы не сможете выполнять какую-либо другую работу
@phuzi, извини. Ты прав. Я обновил структуру.
After I called the generator function and iterated through it
покажи этот код
Вы не можете сказать, была ли выполнена произвольная функция, но вы можете сказать, выполнялась ли она в вашей обернутой версии. Рассмотрите возможность инкапсуляции их в триггерную функцию, например function didRun(fn, hook)
, которая вызывает hook()
, а затем выполняет fn()
при вызове, пересылая все аргументы.
@Samathingamajig, нет, они не возвращают обещания, но я могу обернуть их в обещание (). Это то, что я пытался сделать изначально.
@Christoph, заключающий их в обещание, ничего не меняет, вы можете передать управление только из-за асинхронных вещей, таких как setTimeout
, fetch
, AsyncLocalStorage
, функций, которые возвращают обещания, функций, которые принимают аргументы обратного вызова и т. д.
Ваш пример нуждается в более подробной информации, показывающей реальные примеры того, что представляют собой функции и что вы на самом деле пробовали. Не говорите просто «мои попытки заглушить простой пример не увенчались успехом», покажите свои попытки. Отредактируйте свой вопрос, не показывайте код в комментариях!
Непонятно, почему ваш массив данных содержит callback
s, что эти функции делают и когда и кем они должны вызываться. Пожалуйста, уточните или добавьте их код.
@Bergi, как я уже упоминал в своем вопросе, обратный вызов будет использоваться как часть обработчика события onclick. Когда это событие происходит, выполняется обратный вызов. Теперь я думаю, что обработчик событий на самом деле должен быть функцией, которая обертывает обратный вызов. И он устанавливает обещание, которое разрешается только тогда, когда это событие действительно происходит и выполняется callback().
@Christoph Да, чтобы определить, что событие было запущено и был вызван обратный вызов, вам нужно обернуть обратный вызов перед настройкой прослушивателя событий. Но это все еще не отвечает на вопрос, что на самом деле делает обратный вызов (он действительно нужен? Содержит ли он бизнес-логику?), и где находится код, который регистрирует его (или обернутую функцию) в качестве обработчика кликов.
Также действительно непонятно, зачем вам здесь функция итератора или генератора. Просто поместить логику и ожидание в цикл, который перебирает массив?
@Bergi, да, он содержит бизнес-логику. Он используется для отслеживания того факта, что пользователю было представлено диалоговое окно, и он фактически щелкнул кнопку/ссылку, чтобы закрыть его.
@ Берги, это честно. Я не уверен на 100%, что мне нужен генератор. Просто кажется, что генераторы - это способ перебирать массив и ждать, пока не произойдет конкретное событие (не событие типа dom; например, Promise.resolve()), прежде чем оно перейдет к следующей итерации. Если я могу каким-то образом убедиться, что обещание не выполняется до определенного момента времени (например, когда нажата кнопка), то я могу сделать так, чтобы цикл мог оставаться в стазисе (ожидая yield
или подобного) до тех пор, пока затем.
Вы должны использовать либо обратные вызовы с бизнес-логикой, либо функцию-генератор (или просто функцию async
с некоторыми await
) с бизнес-логикой, но не то и другое одновременно.
Если бы вы могли добавить к своему вопросу код бизнес-логики (при необходимости сокращенный) с псевдокодом, где вы хотите подождать, я могу написать ответ с ним.
@Bergi, обратный вызов выполнит fetch
конечную точку, используемую для отслеживания того факта, что пользователь закрыл диалог. Но я хочу вызывать это fetch
только тогда, когда запускается событие клика. То есть, *частично*, где в игру вступает ракурс промиса. Либо .then()
вызывает Promise.resolve()
из созданного промиса, либо мы просто await
fetch
, я не уверен. Тем не менее, по-прежнему имеет место случай, когда обратный вызов (либо сам по себе, либо функция, которая его обертывает) будет выполняться только для события onclick и только затем возвращает управление обратно в цикл для итерации к следующему элементу массива.
Итак, опять же, X диалогов будут представлены для X элементов массива, но только после закрытия предыдущего диалога.
Вы можете использовать асинхронные итераторы:
const arr = [{
"content": "FIRST_CONTENT_HERE",
"callback": function callback (_) { return _; }
},
{
"content": "SECOND_CONTENT_HERE",
"callback": function callback (_) { return _; }
},
{
"content": "THIRD_CONTENT_HERE",
"callback": function callback (_) { return Promise.resolve(_); }
},
{
"content": "FOUR_CONTENT_HERE",
"callback": function callback (_) { return _; }
}
];
async function* asyncGenerator(tasks) {
for (const task of tasks) {
yield task.callback(task.content);
}
}
;(async function() {
for await (const txt of asyncGenerator(arr)) {
console.info('->', txt);
}
})();
Вероятно, это правильный ответ, но обратите внимание, что при таком подходе вызывающий код должен дождаться разрешения каждого промиса перед запросом следующего элемента из генератора.
Похоже, это не отвечает на вопрос, поскольку нет ни прослушивателей событий, ни кода, ожидающего чего-либо.
Я хочу создать генератор для итерации по этому массиву, но не возвращать (выход?) управление обратно к следующей итерации, пока эта функция обратного вызова не будет выполнена.
Проблема в том, что генераторы и промисы имеют принципиально разные предположения. Обещание, по сути, «Я буду работать над этим, пока не закончу, и перезвоню вам, когда оно будет закончено», в то время как генератор, по сути, «Дайте мне знать, когда вы будете готовы к следующему».
Если вы используете генератор, вы должны вернуть значение при следующем вызове функции... вы не можете ждать. Итак, вопрос в том, насколько важно для вас дождаться завершения первого вызова, прежде чем начинать второй вызов?
Если вы можете обрабатывать все вызовы одновременно, то ваш генератор может просто возвращать промисы, а ваш вызывающий код может взять на себя ответственность за их выполнение.
Однако, если важно, чтобы эти выноски обрабатывались последовательно по одному, то вы не можете использовать генератор. Однако вы можете использовать обратный вызов.
Вероятно, это не совсем то, на что вы надеялись, но если вы должны убедиться, что выноски вызываются последовательно по одному, то это простое решение.
const arr = [{
"content": "FIRST_CONTENT_HERE",
"callback": function callback () {}
},
{
"content": "SECOND_CONTENT_HERE",
"callback": function callback () {}
},
{
"content": "THIRD_CONTENT_HERE",
"callback": function callback () {}
},
{
"content": "THIRD_CONTENT_HERE",
"callback": function callback () {}
}
];
async function processList(itemCallback) {
for (const item of arr) {
await item.callback();
itemCallback(item);
}
}
async function callingCode() {
await processList(item => {
// Do something with item...
});
}
Если генератор больше ориентирован на удобство и синтаксическую красоту, чем на производительность или технические требования, то вы можете просто вернуть промисы и позволить вызывающему коду обработать их:
const arr = [{
"content": "FIRST_CONTENT_HERE",
"callback": async function callback () {}
},
{
"content": "SECOND_CONTENT_HERE",
"callback": async function callback () {}
},
{
"content": "THIRD_CONTENT_HERE",
"callback": async function callback () {}
},
{
"content": "THIRD_CONTENT_HERE",
"callback": async function callback () {}
}
];
async function* getList() {
for (const item of arr) {
yield item.callback().then(() => item);
}
}
async function callingCode() {
for await (const item of getList()) {
// Do something with item
}
}
«Однако в этом случае вызывающий код должен будет дождаться разрешения промиса». Это то, что я хочу, потому что это вызывающий код, который будет настраивать и запускать диалог, к которому будет привязана функция обратного вызова (через обработчик onclick)
Возможно, но ваш вопрос немного запутан: «Я хочу создать генератор для итерации по этому массиву, но не возвращать (выход?) управление обратно к следующей итерации, пока эта функция обратного вызова не будет выполнена». У генератора нет возможности не сдаваться. Он должен либо уступить, либо вернуться и закончить. Итак, либо вы должны дать обещание, либо вы не можете использовать генератор.
Я обновил свой ответ, чтобы устранить этот нюанс.
«Итак, либо вы должны дать Обещание, либо вы не можете использовать генератор». Вот почему я убежден, что промисы должны быть где-то в моем решении. Я просто не знаю, где и как. Мои кусочки головоломки не складывались воедино.
Да, промисы имеют большое значение. Таким образом, основной вопрос заключается не в том, «обещает или нет», а в том, «есть ли техническая причина, препятствующая тому, чтобы вызывающий код запускал все вызовы одновременно?» Если ваш код/сервис может обрабатывать все эти одновременные вызовы, просто верните промисы и позвольте вызывающему коду разобраться с этим. Если нет, то не используйте генератор.
Вы можете позволить обратному вызову быть функцией, которая открывает диалог и возвращает обещание. Это обещание должно разрешиться при вызове обработчика кликов.
Вот пример:
promiseDialog
— это такая функция обратного вызова: для отображения требуется заголовок, а диалоговое окно позволяет пользователю ввести некоторые данные и закрыть диалоговое окно.
Вы можете сделать так, чтобы функция драйвера повторяла заданный вами массив, но каждый раз с promiseDialog
в качестве обратного вызова и давала обещание, что этот обратный вызов возвращает.
Наконец, вы можете использовать эти полученные промисы с помощью цикла for await
:
(Примечание: диалог на самом деле не является модальным диалогом, но он не имеет отношения к демонстрации)
function dialogPromise(title) {
const dialog = document.querySelector("#dialog");
dialog.querySelector("p").textContent = title;
dialog.style.display = "inline-block";
const button = dialog.querySelector("button");
const input = dialog.querySelector("input");
input.value = "";
input.focus();
return new Promise(function(resolve) {
button.addEventListener("click", function (e) {
e.preventDefault();
dialog.style.display = "none";
// Resolve when paint cycle has happened (to hide dialog)
requestAnimationFrame(() => requestAnimationFrame(() =>
resolve(input.value)
));
}, { once: true });
});
}
async function* chainDialogs(tasks) {
for (const task of tasks) {
yield task.callback(task.content);
}
}
(async function() {
const arr = [
{ "content": "FIRST_CONTENT_HERE", "callback": dialogPromise },
{ "content": "SECOND_CONTENT_HERE", "callback": dialogPromise },
{ "content": "THIRD_CONTENT_HERE", "callback": dialogPromise },
{ "content": "FOUR_CONTENT_HERE", "callback": dialogPromise }
];
for await (const result of chainDialogs(arr)) {
console.info(result);
}
})();
#dialog { position: absolute; display: none; margin: 5vw ; padding: 5px; border: 1px solid; background: #ee8 }
<form id = "dialog">
<p>Test dialog</p>
Your answer: <input>
<button>OK</button>
</form>
Это именно то, что я ищу. Большое спасибо. Большое спасибо всем, кто прислал ответы и/или прокомментировал. Я узнал много нового из ваших комментариев, и я ценю это. Спасибо.
выглядит примерно так" Можете ли вы убедиться, что это выглядит недействительным - элементы массива не имеют имен свойств.