zoukankan      html  css  js  c++  java
  • JS多异步之间的协作方案

    场景:使用工具函数downloadAllAsync接收一个URL数组并下载所有文件,结果返回一个存储了文件内容的数组,每个URL对应一个字符串。

    好处:downloadAllAsync并不只有清理嵌套回调函数的好处,其主要好处是并行下载文件。我们可以在同一个事件循环中一次启动所有文件的下载,而不用等待每个文件完成下载。

    并行逻辑是微妙的,很容易出错。下面的实现有一个隐蔽的缺陷。

    function downloadAllAsync(urls, onsuccess, onerror) {
        var result = [],
            len = urls.length;
    
        if(len === 0) {                         // 如果请求路径为空, 不执行下面的程序
            // 绝不要同步地调用异步的回调函数
            setTimeout(onsuccess.bind(null, result), 0);
            return;
        }
    
        urls.forEach(function(url) {
            downloadAsync(url, function(r) {
                if(result) {
                    result.push(r);             // race condition
                    // 根据提供的url, 所有文件数据被成功下载后,执行onsuccess程序
                    result.length === len && onsuccess(result);
                }
            }, function(e) {
                if (result) {
                    result = null;              // 在错误的情况下, 确保onerror只执行一次
                    onerror(e);
                }
            })
        })
    }
    

    如果有多个下载失败,我们设置了result数组为null,从而保证onerror只被调用一次。即在第一次错误发生时。

    downloadAllAsync函数实现的是一旦下载完成就立即将中间结果保存在result数组的末尾。因此,陷阱是保存下载文件内容的数组的顺序是未知的。几乎不能正确使用这样的API,因为调用者无法找出哪个结果对应哪个文件。

    疑问:为什么使用setTimeout函数来调用onsuccess回调函数,而不是直接调用它

    我们将请求的中间结果存储在原始的索引位置来达到预期结果

    function downloadAllAsync(urls, onsuccess, onerror) {
        var result = [],
            len = urls.length;
    
        if(len === 0) {
            setTimeout(onsuccess.bind(null, result), 0);
            return;
        }
    
        urls.forEach(function(url, index) {
            downloadAsync(url, function(r) {
                if(result) {
                    result[index] = r;          // store at fixed index
    
                    result.length === len && onsuccess(result);             // race condition
                }
            }, function(e) {
                if (result) {
                    result = null;
                    onerror(e);
                }
            })
        })
    }
    

    该实现利用了foreach回到函数的第二个参数。该参数为当前迭代提供的数组索引。不幸的是,这仍然不正确。

    数组更新契约,即设置一个索引属性,总是确保数组的length属性大于索引。

    正确的实现应用了一个计数器来追踪正在进行的操作数量。

    function downloadAllAsync(urls, onsuccess, onerror) {
        var result = [],
            pending = urls.length;
    
        if(pending === 0) {
            setTimeout(onsuccess.bind(null, result), 0);
            return;
        }
    
        urls.forEach(function(url, index) {
            downloadAsync(url, function(r) {
                if(result) {
                    result[index] = r;          // store at fixed index
                    // pending -= 1;               // register the success
                    // pending === 0 && onsuccess(result);             // race condition
    
                    --padding || onsuccess(result);
                }
            }, function(e) {
                if (result) {
                    result = null;
                    onerror(e);
                }
            })
        })
    }
    

    现在整个世界都太平了,不论事情以什么样的顺序发生,pending计数器都能准确地指出何时所有的事件会被完成,并以预期的顺序返回完整的结果。

    参考:编写高质量JS代码68个有效方法

    更新: 2014/05/14

    一般而言,事件与侦听器关系是一对多,但在异步编程中,也会出现事件与侦听器的关系是多对一的情况,话句话说,一个业务逻辑可能依赖多个回调或事件传递的结果。例如,在网页渲染的过程中,通常需要数据、模板、资源文件,这三者互相之间并不依赖,但最终渲染结果中三者缺一不可。如果采用默认的异步方法调用,程序也许将会如下所示:

    fs.readFile(template_path, 'utf8', function(err, template) {
        db.query(sql, function(err, data) {
            l10n.get(function(err, resourse) {
                // TODO
            });
        });
    });
    

    这在结果的保证上是没有问题的,问题在于这并没有利用好异步I/O带来的并发优势。这是异步编程的典型问题,为了实现最终结果的处理而导致可以并行调用但实际只能串行执行。

    var count = 0;
    var result = {};                    // 存放查询结果
    var done = function(key, value) {
        result[key] = value;
        count++;
        count === 3 && render(result);
    };
    
    fs.readFile(template_path, 'utf8', function(err, template) {
        done('template', template);
    });
    db.query(sql, function(err, data) {
        done('data', data);
    });
    l10n.get(function(err, resourse) {
        done('resourse', resourse);
    });
    
  • 相关阅读:
    Java实现 蓝桥杯VIP 算法提高 3000米排名预测
    Java实现 蓝桥杯VIP 算法提高 3000米排名预测
    Java实现 蓝桥杯VIP 算法提高 班级排名
    Java实现 蓝桥杯VIP 算法提高 班级排名
    Java实现 蓝桥杯VIP 算法提高 班级排名
    Java实现 蓝桥杯VIP 算法提高 班级排名
    当时忍住就好了(要抵住诱惑,也不要在极端情绪下做出决定,一切向前看)
    MFC中的模态对话框与非模态对话框,模态对话框测试
    在iOS中创建静态库
    delphi三层架构(使用SATRDA改造,客户端代码不变)
  • 原文地址:https://www.cnblogs.com/mackxu/p/downloadAllAsync.html
Copyright © 2011-2022 走看看