zoukankan      html  css  js  c++  java
  • 异步操作概述

    原文地址:https://wangdoc.com/javascript/

    单线程模型

    单线程模型指的是,JavaScript只在一个线程上运行。也就是说,JavaScript同时只能执行一个任务,其他的任务都必须在后面排队等待。

    同步任务和异步任务

    程序里面的所有任务,可以分为两类:同步任务和异步任务。

    任务队列和事件循环

    JavaScript运行时,除了一个正在运行的主线程,引擎还提供一个任务队列,里面是各种需要当前程序处理的异步任务。(实际上,根据异步任务的类型,存在多个任务队列)
    首先主线程会去执行所有的同步任务。等到同步任务全部执行完,就会去看任务队列里面的异步任务。如果满足条件,那么异步任务就重新进入主线程开始执行,这时它就变成同步任务了。等执行完,下一个异步任务在进入主线程开始执行。一旦任务队列清空,程序就结束执行。
    异步任务的写法通常是回调函数。一旦异步任务重新进入主线程,就会执行对象的回调函数。如果一个异步任务没有回调函数,就不会进入任务队列。
    JavaScript引擎怎么知道异步任务有没有结果,能不能进入主线程呢?答案是引擎在不停的检查,一遍又一遍,只要同步任务执行完了,引擎就会去检查那些挂起来的异步任务,是不是可以进入主线程了。这种循环检查的机制,就叫做事件循环。

    异步操作的模式

    回调函数

    回调函数是异步操作最基本的方法。

    function f1(callback) {
        // ...
        callback();
    }
    
    function f2() {
        // ...
    }
    
    f1(f2);
    

    回调函数的优点是简单、容易理解和实现,缺点是不利于代码的阅读和维护,各个部分之间高度耦合。

    事件监听

    另一种思路是采用事件驱动模式。异步任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
    还是以f1f2为例。首先,为f1绑定一个时间(这里采用的jQuery的写法)。

    f1.on("done", f2);
    

    上面代码的意思是,当f1发生done事件,就执行f2。然后对f1进行改写:

    function f1() {
        setTimeout(function () {
            // ...
            f1.trigger("done");
        }, 1000);
    }
    

    上面代码中,f1.trigger("done")表示,执行完成后,立即触发done事件,从而开始执行f2
    这种方法的优点时比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以去耦合,有利于实现模块化。

    发布/订阅

    事件完全可以理解成信号,如果存在一个信号中心,某个任务执行完成,就想信号中心发布一个信号,其他任务可以向信号中心订阅这个信号。这就叫做发布订阅模式,又称观察者模式。
    这个模式有多种实现,下面采用的是Ben Alman的Tiny Pub/Sub,这是jQuery的一个插件。
    首先f2向信号中心jQuery订阅done信号。

    jQuery.subscribe("done", f2);
    

    然后f1做如下改写。

    function f1() {
        setTimeout(function () {
            //...
            jQuery.publish("done");
        }, 1000);
    }
    

    上面代码中,jQuery.publish("done")的意思是,f1执行完成后,向信号中心jQuery发布done信号,从而引发f2的执行。
    f2执行完成后,可以取消订阅。

    jQuery.unsubscribe("done", f2);
    

    异步操作的流程控制

    如果有多个异步操作,就存在一个流程控制的问题:如何确定异步操作执行的顺序,以及如何保证遵守这种顺序。

    function async(arg, callback) {
        console.log("参数为 " + arg + " , 1秒后返回结果");
        setTimeout(function () { callback(arg * 2); }, 1000);
    }
    

    上面代码的async函数是一个异步任务,非常耗时,每次执行需要1秒才能完成,然后在调用回调函数。
    如果有六个这样的异步任务,需要全部完成后,才能执行最后的final函数。请问应该如何安排操作流程?

    function final(value) {
        console.log("完成: ", value);
    }
    
    async(1, function (value) {
        async(value, function (value) {
            async(value, function (value) {
                async(value, function (value) {
                    async(value, function (value) {
                        async(value, final);
                    });
                });
            });
        });
    });
    

    上面代码中,六个回调函数的嵌套,不仅写起来麻烦,容易出错,而且难以维护。

    串行执行

    我们可以编写一个流程控制函数,让它来控制异步任务,一个任务完成以后,再执行另一个。这就叫串行执行。

    var items = [1, 2, 3, 4, 5, 6];
    var results = [];
    
    function async(arg, callback) {
        console.log("参数为 " + arg + " ,1秒后返回结果");
        setTimeout(function () {
            callback(arg * 2);
        }, 1000);
    }
    
    function final(value) {
        console.log("完成:", value);
    }
    
    function series(item) {
        if(item) {
            async(item, function (result) {
                results.push(result);
                return series(items.shift());
            });
        } else {
            return final(results[results.length - 1]);
        }
    }
    
    series(items.shift());
    

    注意,上面的写法需要六秒,才能完成整个脚本。

    并行执行

    流程控制函数也可以是并行执行,即所有异步任务同时执行,等到全部完成以后,才执行final函数。

    var items = [1, 2, 3, 4, 5, 6];
    var results = [];
    
    function async(arg, callback) {
        console.log("参数为 " + arg + " , 1秒后返回结果");
        setTimeout(function () {
            callback(arg * 2);
        }, 1000);
    }
    
    function final(value) {
        console.log("完成:", value);
    }
    
    items.forEach(function (item) {
        async(item, function (result) {
            results.push(result);
            if (results.length === items.length) {
                final(results[results.length - 1]);
            }
        });
    });
    

    上面代码中,forEach方法会同时发起六个异步任务,等到它们全部完成以后才会执行final函数。
    相比而言,上面的写法只要一秒,就能完成脚本。这就是说,并行执行的效率比较高,但是在于如果并行的任务较多,很容易耗尽系统资源,拖慢运行速度。因此有个第三种流程控制方式。

    并行与串行的结合

    所谓并行和串行的结合,就是设置一个门槛,每次最多只能并行执行n个异步任务,这样就避免了过分占用资源。

    var items = [1, 2, 3, 4, 5, 6];
    var results = [];
    var running = 0;
    var limit = 2;
    
    function async(arg, callback) {
        console.log("参数为 " + arg + " , 1秒后返回结果");
        setTimeout(function () {
            callback(arg * 2);
        }, 1000);
    }
    
    function final(value) {
        console.log("完成:", value);
    }
    
    function launcher() {
        while (running < limit && items.length > 0) {
            var item = items.shift();
            async(item. function(result) {
                results.push(result);
                running--;
                if (items.length > 0) {
                    launcher();
                } else if (running === 0) {
                    final(results);
                }
            });
            running++;
        }
    }
    
    launcher();
    
  • 相关阅读:
    洛谷 P5057 [CQOI2006]简单题 题解
    洛谷 P3368 【模板】树状数组 2 题解
    洛谷 P3374 【模板】树状数组 1 题解
    洛谷 P2023 [AHOI2009]维护序列 题解
    洛谷 P2253 好一个一中腰鼓! 题解
    求最长不下降/上升/下降/不上升子序列
    [SQL Server]Index/deadlock
    Ubuntu 14.04下从源码安装qt4.x
    Ubuntu系统下Import cv2提示no modules ...错误
    Ubuntu 14.04下安装CUDA8.0
  • 原文地址:https://www.cnblogs.com/chris-jichen/p/10154316.html
Copyright © 2011-2022 走看看