2010-11-25 3 views
1

Я пытаюсь написать функцию JavaScript (с JQuery, если вы хотите):Как переносить выполнение асинхронных (callback-based) функций в синхронную функцию в Javascript?

function fetchItem(itemId) { return /* ??? */; } 

Эта функция опирается на второй, предопределенной и unmodifyable функции, которая выглядит следующим образом:

function load(callback) { /* ... */ } 

Эта функция асинхронна. После его вызова он извлекает n элементов через XHR, а затем, когда они прибыли, сохраняет их в DOM, а затем вызывает обратный вызов.

fetchItem использует простой селектор JQuery (ненужный здесь), чтобы проверить DOM для элемента с itemId и вызывает load если элемент не существует. Промыть и повторить.

Моя проблема заключается в том, что я хочу, чтобы обернуть несколько асинхронных вызовов load в моей синхронной fetchItem функции, которая должна возвращать элемент DOM с itemId после того, как он сделал достаточно load звонки.

псевдокоде, если load был синхронным:

function fetchItem(itemId): 
    while not dom.contains(itemId): 
     load() 
    return dom.find(itemId) 

Мои первые попытки сделать это в Javascript, который, вероятно, отображать много неправильных представлений о замыканий в JavaScript и модели выполнения:;)

function fetchItem(itemId) { 
    var match = undefined; 

    function finder() { 
     match = $(...).get(0); 
     if(!match) { 
      load(finder); 
     } 
    } 
    finder(); 

    return match; 
} 

Очевидно, что это не удается, поскольку return выполняется до первого обратного вызова. Кроме того, как вы можете видеть, у меня возникли проблемы с получением match обратно до fetchItem. Правильно ли он защищен закрытием здесь? Будет ли это работать, если fetchItem выполнялся несколько раз параллельно, предполагая, что load поддерживает это (и не смешивает DOM)?

Я, вероятно, отсутствует совершенно хороший образец здесь, но я не знаю, что Google для ...

ответ

0

Кажется, все согласны с тем, что мне нужно ввести свой собственный обратный вызов, так вот мой (до сих пор окончательный) рабочий раствор:

var MAX_FETCH_MORE = 3; 

/* 
* Searches for itemId, loading more items up to MAX_FETCH_MORE times if necessary. When 
* the item has been found or the maximum reload count has been reached, the callback 
* is invoked, which is passed the DOM object of the item wrapped in a jQuery object, or 
* undefined. 
*/ 
function executeWithItem(itemId, callback, fetchCycleCounter) { 
    // initialize fetchCycleCounter on first iteration 
    if(!fetchCycleCounter) fetchCycleCounter = 0; 
    console.debug('iteration ' + fetchCycleCounter + '/' + MAX_FETCH_MORE); 

    // try to find the item in the DOM 
    match = $('div[data-item-id="' + itemId + '"]').get(0); 
    if(match) { 
     // if it has been found, invoke the callback, then terminate 
     console.debug('found: ' + match); 
     callback($(match)); 
    } else if(!match && fetchCycleCounter < MAX_FETCH_MORE) { 
     // if it has not been found, but we may still reload, call load() and pass it 
     // this function as the callback 
     console.debug('fetching more...'); 
     load(function() {executeWithItem(itemId, callback, fetchCycleCounter+1);}); 
    } else { 
     // give up after MAX_FETCH_MORE attempts, maybe the item is gone 
     console.debug('giving up search'); 
    } 
} 

// example invocation 
executeWithItem('itemA01', function(item) { 
    // do stuff with it 
    item.fadeOut(10000); 
}); 

Спасибо всем, для поощрения меня ввести другой обратный вызов, он не имеет получилось так плохо. :)

0

Это невозможно. Вы не можете создавать синхронность от асинхронности. Почему бы вам не добавить обратный вызов к вашей функции fetchItem?

+0

Обратный вызов сам создает синхронность от асинхронной процедуры, не так ли?Одна вещь происходит за другой. – Orbling 2010-11-25 19:54:04

2

Вам нужно сделать fetchItems асинхр слишком и предоставить ему обратный вызов, что-то вроде этого, вероятно, следует работать (предупреждение непроверенным!):

function fetchItems(itemIDS, callback, matches) { 
    if (!matches) { // init the result list 
     matches = []; 
    } 

    // fetch until we got'em all 
    if (itemIDS.length > 0) { 
     var id = itemIDS[0]; // get the first id in the queue 
     var match = $(id).get(0); 

     // not found, call load again 
     if (!match) { 
      load(function() { 
       fetchItems(itemIDS, callback, matches); 
      }); 

     // found, update results and call fetchItems again to get the next one 
     } else { 
      matches.push(match); // push the current match to the results 
      itemIDS.shift(); // remove the current id form the queue 
      fetchItems(itemIDS, callback, matches); 
     } 

    // we have all items, call the callback and supply the matches 
    } else { 
     callback(matches); 
    } 
} 

fetchItems(['#foo', '#bar', '#test'], function(matches) { 
    console.log(matches); 
}) 
1

я просто отдал свою функцию fetchItem как обратный вызов для загрузки. Как это:

function fetchItem(itemId, callback): 
    if not dom.contains(itemId): 
     load(fetchItem) 
    else: 
     callback(dom.find(itemId)) 

обратного вызова() является функцией, которая делает остальную часть работы, когда необходимый элемент появляется в DOM.

Смежные вопросы