Контроль streamа вложенных исполнений

Я прочитал десятки ответов, связанных с обратными вызовами, обещаниями и другими способами контроля streamа, но я не могу все еще обвести голову вокруг этой задачи, очевидно, из-за моей недостаточной компетентности.

У меня есть вложенная проблема:

  1. В test_1() (и других функциях) я хотел бы убедиться, что строки добавляются в таблицу в соответствии с порядком, в котором элементы находятся в объекте;
  2. Я хотел бы выполнить либо test_2, либо test_3 (или оба после друг друга) только после завершения теста_1. На самом деле правая последовательность будет известна только во время выполнения (появится переключатель с возможными последовательностями, такими как 1,2,3 или 1,3,2 или 1,2,1,3 или 1,3,3,2, так далее…)

Код:

 $(function () { // create table tbl = document.createElement('table'); tbl.className = "mainTbl"; $("body").append(tbl); }); function test_1() { $.each(obj, function () { var img = new Image(); img.onload = function () { // add row of data to table var row = tbl.insertRow(-1); var c1 = row.insertCell(0); c1.innerHTML = "loaded"; }; img.onerror = function () { // add row of data to table var row = tbl.insertRow(-1); var c1 = row.insertCell(0); c1.innerHTML = "not loaded"; }; img.src = this.url; }); } function test_2() { $.each(obj, function () { var img = new Image(); img.onload = function () { // add row of data to table var row = tbl.insertRow(-1); var c1 = row.insertCell(0); c1.innerHTML = "loaded"; }; img.onerror = function () { // add row of data to table var row = tbl.insertRow(-1); var c1 = row.insertCell(0); c1.innerHTML = "not loaded"; }; img.src = this.url; }); } function test_3() { $.each(obj, function () { var img = new Image(); img.onload = function () { // add row of data to table var row = tbl.insertRow(-1); var c1 = row.insertCell(0); c1.innerHTML = "loaded"; }; img.onerror = function () { // add row of data to table var row = tbl.insertRow(-1); var c1 = row.insertCell(0); c1.innerHTML = "not loaded"; }; img.src = this.url; }); } 

Я знаю, что вызов функций в последовательности не работает, поскольку они не ждут друг друга … Я думаю, что обещания – это путь, но я не могу найти правильную комбинацию, и документация слишком сложна для моего навыки.

Каков наилучший способ структурирования кода, чтобы он выполнялся в правильном порядке?

Вы очень затруднили ответ на этот вопрос, потому что ваш код настолько абстрактен и не хватает тонны соответствующих деталей, например, как вы называете test_1() и test_2() и т. Д. …, что вы делаете с изображениями, что obj есть и т. д. Я знаю, что вы пытались упростить вещи, не обращая внимания на детали, но на самом деле вы просто сделали это слишком абстрактным, чтобы знать, как отвечать или какую проблему вы действительно пытаетесь решить.

В JS вы не можете назвать что-то и сказать, чтобы он подождал, пока что-то еще не будет сделано. Вы можете сказать test_2() когда выполнено test_1() . Или вы можете зарегистрировать последовательность функций, и в любое время, test_n() будет выполнено любое test_n() , она может вызывать следующую функцию в последовательности. Или вы можете переключиться на использование обещаний и использовать функциональные возможности обещания для планирования асинхронных действий, которые будут выполняться последовательно.

В духе вашего абстрактного обсуждения, вот абстрактный способ упорядочения вещей. Я сделал два основных изменения:

  1. Строки и ячейки добавляются синхронно, когда вы выполняете итерацию obj чтобы гарантировать, что они будут добавлены в правильном порядке.

  2. Отдельные функции запланированы, и test_2() не вызывается до тех пор, пока все функции не запланированы до его завершения. Это организуется без вызова определенных имен функций, создавая упорядоченный список функций, а затем каждая функция использует seq.increment() и seq.decrement() чтобы секвенсер отслеживал, когда это делается, чтобы вызвать следующую функцию.

Я предполагаю, что фактическая реализация не обязательно должна быть такой общей, и более конкретное решение может быть проще, но поскольку вы полностью обсудили обсуждение, это конкретный способ получить строки, вставленные в порядок, и абстрактный способ удостовериться, что функции вызываются по порядку, а test_2() не вызывается, пока все изображения не закончили с test_1() .

 // an object to maintain a list of functions that can be called in sequence // and to manage a completion count for each one function Sequencer() { this.list = []; this.cnt = 0; } Sequencer.prototype.add = function(/* list of function references here */) { this.list.push.apply(this.list, arguments); } Sequencer.prototype.next = function() { var fn = this.list.shift(); if (fn) { fn(this); } } Sequencer.prototype.increment = function(n) { n = n || 1; this.cnt += n; } // when calling .decrement(), if the count gets to zero // then the next function in the sequence will be called Sequencer.prototype.decrement = function(n) { n = n || 1; this.cnt -= n; if (this.cnt <= 0) { this.cnt = 0; this.next(); } } // your actual functions using the sequencer object function test_1(seq) { seq.increment(); $.each(obj, function () { seq.increment(); var img = new Image(); var row = tbl.insertRow(-1); var c1 = row.insertCell(0); img.onload = function () { // add row of data to table c1.innerHTML = "loaded"; seq.decrement(); }; img.onerror = function () { // add row of data to table c1.innerHTML = "not loaded"; seq.decrement(); }; img.src = this.url; }); seq.decrement(); } function test_2(seq) { seq.increment(); $.each(obj, function () { seq.increment(); var img = new Image(); var row = tbl.insertRow(-1); var c1 = row.insertCell(0); img.onload = function () { // add row of data to table c1.innerHTML = "loaded"; seq.decrement(); }; img.onerror = function () { // add row of data to table c1.innerHTML = "not loaded"; seq.decrement(); }; img.src = this.url; }); seq.decrement(); } function test_3(seq) { seq.increment(); $.each(obj, function () { seq.increment(); var img = new Image(); var row = tbl.insertRow(-1); var c1 = row.insertCell(0); img.onload = function () { // add row of data to table c1.innerHTML = "loaded"; seq.decrement(); }; img.onerror = function () { // add row of data to table c1.innerHTML = "not loaded"; seq.decrement(); }; img.src = this.url; }); seq.decrement(); } // code to run these in sequence var s = new Sequencer(); // add all the functions to the sequencer s.add(test_1, test_2, test_3); // call the first one to initiate the process s.next(); 

FYI, у меня лично никогда не было бы кода test_1, test_2 и test_3, который бы выглядел так почти идентично, как если бы я включил его в общую часть, к которой я мог бы привести аргументы, но вы сделали этот реферат, поэтому я не знаю, как фактор то, что вы не указали никаких различий или особенностей.

Крис, я вижу, что у вас есть ответ, но есть другие способы сделать такие вещи, особенно, чтобы написать ваши функции test_ , чтобы они вернули promise загружать или не загружать все изображения и использовать эти обещания для асинхронного последовательность.

В идеале у нас был бы ansible нам метод $.allSettled() , но поскольку jQuery не имеет такого, нам нужно обходное решение. К счастью, обходной путь очень прост в этом случае.

Для компактности я использую jQuery как можно больше и получаю следующее:

 $(function () { var $tbl = $('
').appendTo("body"); function test_1() { //First, use $.map() to build an array of promises from `obj`. var promises = $.map(obj, function() { return $.Deferred(function(dfrd) { var $c1 = $("").appendTo($("").prependTo($tbl));//a concise one-liner to make the required table elements and insert them in the DOM var img = new Image(); img.onload = dfrd.resolve;//a jQuery Deferred's resolve method is "detachable" img.onerror = dfrd.reject;//a jQuery Deferred's reject method is "detachable" img.src = this.url; }).then(function() {//the workaround for lack of an allSettled method is to return a resolved promise from both the done and fail callbacks of a .then() . $c1.html("loaded"); return $.when(1);//a resolved promise (resolved with 1, though it's not used) }, function() { $c1.html("not loaded"); return $.when(0);//a resolved promise (resolved with 0, though it's not used) }); }); //Now return a new promise, which will be resolved when all images are "settled" (loaded state or error state). return $.when.apply(null, promises); } function test_2() { // identical to test_1(), or a variant as required } function test_3() { // identical to test_1(), or a variant as required } // Now, build your sequence(s) as array(s). The exact way in which you do this will depend on your application. var sequence = [test_1, test_2, test_1, test_3];//some arbitrary sequence of functions, each of which, when executed, will return a promise. // To run the seqnence, you simply exploit `array.reduce()` to build and execute a `.then()` chain, as follows : sequence.reduce(function(promise, fn) { return promise.then(function(result) { return fn(); }); }, $.when()); });

все непроверенные

С момента появления .reduce() это быстро становится де-факто решением этого типа проблемы и, возможно, больше похоже на то, что вы изначально предполагали. В сущности, это все равно. На практике код может быть немного сложнее, например:

  • если вам необходимо передать параметрам функции test_
  • если вам нужно обрабатывать ошибки

Заключительное примечание . Объект javascript представляет собой нескончаемый набор свойств. Если порядок в вашем obj важен, используйте массив элементов, а не объект свойств.