Как обернуть выполнение асинхронных (основанных на обратном вызове) функций в синхронную функцию в Javascript? - PullRequest
1 голос
/ 25 ноября 2010

Я пытаюсь написать функцию в Javascript (с jQuery, если хотите):

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

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

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 ...

Ответы [ 4 ]

2 голосов
/ 25 ноября 2010

Вам также нужно сделать асинхронную функцию 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 голос
/ 25 ноября 2010

Я бы просто дал вашу функцию fetchItem в качестве обратного вызова для загрузки.Например:

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

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

0 голосов
/ 26 ноября 2010

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

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 голосов
/ 25 ноября 2010

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

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