2010-11-25 26 views
1

Je suis en train d'écrire une fonction en Javascript (avec jQuery, si vous voulez):Comment est-ce que j'emballe des exécutions de fonctions asynchrones (basées sur le rappel) dans une fonction synchrone dans Javascript?

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

Cette fonction repose sur une seconde, la fonction prédéfinie et unmodifyable qui ressemble à ceci:

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

Cette fonction est asynchrone. Après l'avoir appelé, il récupère n éléments via XHR, puis quand ils sont arrivés, les stocke dans le DOM, puis appelle le callback.

fetchItem utilise un simple sélecteur jQuery (hors de propos ici) pour vérifier les DOM pour l'élément avec itemId et demande load si l'article est pas encore là. Rincez et répétez.

Mon problème est que je veux envelopper plusieurs appels asynchrones de load dans ma fonction synchrone fetchItem, qui doit retourner l'élément DOM avec itemId après avoir fait assez load appels.

Pseudo code, si load était synchrone:

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

Mes premières tentatives de le faire en Javascript, qui affiche probablement beaucoup d'idées fausses sur les fermetures de Javascript et modèle d'exécution:;)

function fetchItem(itemId) { 
    var match = undefined; 

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

    return match; 
} 

Évidemment, cela échoue parce que le return est exécuté avant le premier rappel. Aussi, comme vous pouvez le voir, j'ai eu quelques problèmes pour obtenir match revenir à fetchItem. Est-il correctement protégé par la fermeture ici? Cela fonctionnerait-il si fetchItem était exécuté plusieurs fois en parallèle, en supposant que load le supporte (et ne mélange pas le DOM)?

Je suis manque probablement un modèle parfaitement bien ici, mais je ne sais pas vraiment quoi google pour ...

Répondre

0

On dirait que tout le monde est d'accord que je dois présenter mon propre rappel, donc voici mon (jusqu'à présent finale) la solution de travail:

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); 
}); 

Merci à tous pour me encourager à introduire un autre rappel, il n'a pas avéré si mauvais. :)

0

qui est impossible. Vous ne pouvez pas créer de synchronisme à partir de l'asynchronisme. Pourquoi n'ajouteriez-vous pas de rappel à votre fonction fetchItem?

+0

Le rappel lui-même crée de la synchrone à partir de la routine asynchrone, n'est-ce pas?Une chose se passe après l'autre. – Orbling

+0

@Orbling: Non, ce n'est pas le cas. Vous ne pouvez pas prédire lequel des deux appels asynchrones finit en premier (_that_ serait synchrone). – jwueller

+0

Non, mais vous pouvez configurer votre code de sorte qu'un morceau de code donné suive un appel asynchrone, ou même après que plusieurs appels asynchrones aient eu lieu. Le seul point de doute est l'ordre de retour lorsque plusieurs appels asynchrones sont lancés. – Orbling

2

Vous devez faire fetchItems async aussi, et fournir un rappel, quelque chose comme cela devrait probablement travailler (avertissement non testé!):

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

Je voudrais simplement donné votre fonction fetchItem comme un rappel à la charge. Comme ceci:

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

callback() est une fonction qui fait le reste du travail lorsque l'élément nécessaire apparaît dans le DOM.