代码之家  ›  专栏  ›  技术社区  ›  Henrik Heimbuerger Kenneth Reitz

如何在Javascript中将异步(基于回调)函数的执行包装成同步函数?

  •  1
  • Henrik Heimbuerger Kenneth Reitz  · 技术社区  · 14 年前

    我试图用Javascript编写一个函数(如果需要,可以使用jQuery):

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

    此函数依赖于第二个预定义且不可修改的函数,如下所示:

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

    这个函数是异步的。调用之后,它通过XHR获取n个项,然后当它们到达时,将它们存储在DOM中,然后调用回调。

    fetchItem 使用一个简单的jQuery选择器(此处不相关)检查元素的DOM itemId 和电话 load 如果这个东西还没有。冲洗并重复。

    我的问题是我想包装多个异步调用 负载 进入我的同步 获取项目 函数,它将返回DOM元素 项目ID 当它足够 负载 电话。

    伪代码,如果 负载 是同步的:

    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被并行执行多次,假设 负载 支持这个(并且不混淆DOM)?

    我可能错过了一个完美的模式,但我真的不知道谷歌是为了什么。。。

    4 回复  |  直到 14 年前
        1
  •  2
  •   Ivo Wetzel    14 年前

    您还需要使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);
    })
    
        2
  •  1
  •   cababunga    14 年前

    我只是将fetchItem函数作为回调函数来加载。这样地:

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

    callback()是一个函数,当DOM中出现必要的元素时,它将执行其余的工作。

        3
  •  0
  •   jwueller    14 年前

    那是不可能的。不能从异步创建同步。为什么不在 fetchItem -功能也一样?

        4
  •  0
  •   Henrik Heimbuerger Kenneth Reitz    14 年前

    似乎每个人都同意我需要引入自己的回调,所以这里是我(到目前为止的最终)的工作解决方案:

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

    感谢大家鼓励我再次回拨电话,结果没那么糟。:)