2013-04-12 3 views
10

Я занимаюсь разработкой в ​​JavaScript довольно долгое время, но пока еще разработчиком ковбоев, поскольку одна из многих вещей, которые всегда преследуют меня, - это синхронизация обратных вызовов JavaScript.Как синхронизировать обратные вызовы JavaScript?

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

Код Пример:

for ... in ... { 
    myFunc1(callback); // callbacks are executed asynchly 
} 

myFunc2(); // can only execute properly if all the myFunc1 callbacks are done 

Предлагаемое решение:

Инициировать счетчика в начале цикла, держащего длину петли, и каждый обратного вызова декременты, что счетчик. Когда счетчик достигает 0, выполните myFunc2. Это, по сути, позволяет callbacks знать, является ли это последним обратным вызовом в последовательности, и если это так, вызовите myFunc2, когда это будет сделано.

Проблемы:

  1. счетчик нужен для каждой такой последовательности в коде, и имеющие бессмысленные счетчики везде не является хорошей практикой.
  2. Если вы помните, как конфликты потоков в классической задаче синхронизации, когда все потоки все вызовы var - в одном и том же var, будут возникать нежелательные результаты. То же самое происходит в JavaScript?

Окончательный Вопрос:

Есть ли лучшее решение?

+0

Должно ли оно происходить параллельно или один за другим? –

+0

Если он захочет это один за другим, то он не будет использовать асинхронные задачи. – jelgh

+0

@jelgh Не обязательно, асинхронные задачи также могут быть сериализованы. –

ответ

11

Хорошей новостью является то, что JavaScript однопоточный; это означает, что решения, как правило, хорошо работают с «совместно используемыми» переменными, т. е. блокировки мьютекса не требуются.

Если вы хотите сериализовать задачи асинхронная, а затем завершения обратного вызова вы можете использовать эту вспомогательную функцию:

function serializeTasks(arr, fn, done) 
{ 
    var current = 0; 

    fn(function iterate() { 
     if (++current < arr.length) { 
      fn(iterate, arr[current]); 
     } else { 
      done(); 
     } 
    }, arr[current]); 
} 

Первый аргумент является массивом значений, который должен быть принят в каждом проходе, то второй Аргумент - это обратный вызов цикла (объясняется ниже), а последним аргументом является функция обратного вызова завершения.

Это функция обратного вызова цикла:

function loopFn(nextTask, value) { 
    myFunc1(value, nextTask); 
} 

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

Давайте предположим, что задача асинхронного выглядит следующим образом:

function myFunc1(value, callback) 
{ 
    console.log(value); 
    callback(); 
} 

Он печатает значение, а затем он вызывает обратный вызов; просто.

Затем, чтобы установить все это в движении:

serializeTasks([1,2, 3], loopFn, function() { 
    console.log('done'); 
}); 

Demo

распараллелить их, вам нужна другая функция:

function parallelizeTasks(arr, fn, done) 
{ 
    var total = arr.length, 
    doneTask = function() { 
     if (--total === 0) { 
     done(); 
     } 
    }; 

    arr.forEach(function(value) { 
     fn(doneTask, value); 
    }); 
} 

И ваша функция цикла будет это (только изменение имени параметра):

function loopFn(doneTask, value) { 
    myFunc1(value, doneTask); 
} 

Demo

+0

Thx @Jack, мне нужно будет пройти через это подробно позже, нужно спать сейчас :) –

+0

@Xavier_Ex Я добавил параллельную версию :) –

+0

Спасибо за внимание и поблагодарить всех за внесение предложений. Все сообщения велики, но только этот пост ответил на мой второй вопрос. Большинство предлагаемых решений связано с использованием какого-либо счетчика, но обещание кажется многообещающим. Я думаю, что я изучу это немного больше :) –

2

Вторая проблема не проблема, поскольку каждая из них находится в отдельной функции, и переменная объявляется правильно (с var); локальные переменные в функциях не мешают друг другу.

Первая проблема - это скорее проблема. Другие люди тоже разозлились, и в итоге они превратили библиотеки в такой шаблон для вас. Мне нравится async. С его помощью ваш код может выглядеть так:

async.each(someArray, myFunc1, myFunc2); 

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

+0

Интересная библиотека; всегда ли предполагается, что функция принимает два параметра, второй - обратный вызов? –

+0

Спасибо за совместное использование, но я думаю, что вторая проблема МОЖЕТ быть проблемой, потому что все функции уменьшают одну и ту же общую переменную (в моем случае, счетчик), но не их собственные локальные переменные. –

+0

@Jack: Да, я так думаю. – icktoofay

2

Вы можете достичь этого, используя отложенный объект jQuery.

var deferred = $.Deferred(); 
var success = function() { 
    // resolve the deferred with your object as the data 
    deferred.resolve({ 
     result:...; 
    }); 
}; 
+0

Спасибо за совместное использование, возможно, мне нужно взглянуть на то, как работает jquery 'offferred'. –

+0

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

1

С этой вспомогательной функции:

function afterAll(callback,what) { 
    what.counter = (what.counter || 0) + 1; 
    return function() { 
    callback(); 
    if(--what.counter == 0) 
     what(); 
    }; 
} 

ваш цикл будет выглядеть следующим образом:

function whenAllDone() { ... } 
for (... in ...) { 
    myFunc1(afterAll(callback,whenAllDone)); 
} 

afterAll здесь создает прокси-функции для обратного вызова, он также уменьшает счетчик.И вызывает функцию WhenAllDone, когда все обратные вызовы завершены.

+0

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

+0

@Xavier_Ex Я не понимаю вашего комментария, любое решение задачи будет использовать счетчики какого-то рода. Рассмотрите функцию 'afterAll' как библиотечную функцию и используйте ее там, где она вам нужна. –

+0

Да, это то, что я хочу узнать, если использовать счетчик - единственный способ. Я знаю, что некоторые проблемы параллелизма могут быть решены совместными подпрограммами, поэтому вы хотите изучить возможности здесь. В конечном счете счетчик не является моим предпочтительным решением, будь то единственный способ или нет. –

1

отдельная нить не всегда гарантирована. не ошибетесь.

Дело 1: Например, если у нас есть 2 функции следующим образом.

var count=0; 
function1(){ 
    alert("this thread will be suspended, count:"+count); 
} 
function2(){ 
    //anything 
    count++; 
    dump(count+"\n"); 
} 

тогда перед FUNCTION1 возвращается, function2 также будет называться, если 1 поток гарантированно, то function2 не будет вызываться до FUNCTION1 возвращается. Вы можете попробовать это. и вы узнаете, что счет растет, когда вас предупреждают.

Корпус 2: с Firefox, хром-код, перед возвращением функции 1 (без предупреждения внутри), можно вызвать еще одну функцию.

Таким образом, блокировка мьютекса действительно необходима.

0

Есть много, много способов добиться этого, я надеюсь, что эти предложения помогут!

Во-первых, я бы превратил обратный вызов в обещание!Вот один из способов сделать это:

function aPromise(arg) { 
    return new Promise((resolve, reject) => { 
     aCallback(arg, (err, result) => { 
      if(err) reject(err); 
      else resolve(result); 
     }); 
    }) 
} 

Далее следует использовать сокращение для обработки элементов массива один за другим!

const arrayOfArg = ["one", "two", "three"]; 
const promise = arrayOfArg.reduce(
    (promise, arg) => promise.then(() => aPromise(arg)), // after the previous promise, return the result of the aPromise function as the next promise 
    Promise.resolve(null) // initial resolved promise 
    ); 
promise.then(() => { 
    // carry on 
}); 

Если вы хотите, чтобы обработать все элементы массива в то же время, использовать карту в Promise.all!

const arrayOfArg = ["one", "two", "three"]; 
const promise = Promise.all(arrayOfArg.map(
    arg => aPromise(arg) 
)); 
promise.then(() => { 
    // carry on 
}); 

Если вы можете использовать асинхра/ждать, то вы можете просто сделать это:

const arrayOfArg = ["one", "two", "three"]; 
for(let arg of arrayOfArg) { 
    await aPromise(arg); // wow 
} 

// carry on 

Вы можете даже использовать мою очень крутую synchronize-async библиотеки так:

const arrayOfArg = ["one", "two", "three"]; 
const context = {}; // can be any kind of object, this is the threadish context 

for(let arg of arrayOfArg) { 
    synchronizeCall(aPromise, arg); // synchronize the calls in the given context 
} 

join(context).then(() => { // join will resolve when all calls in the context are finshed 
    // carry on 
}); 

И последнее, но не менее важное: используйте библиотеку async, если вы действительно не хотите использовать обещания.

const arrayOfArg = ["one", "two", "three"]; 
async.each(arrayOfArg, aCallback, err => { 
    if(err) throw err; // handle the error! 
    // carry on 
}); 
Смежные вопросы