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();
    
  • 相关阅读:
    扫面线模板
    (动态规划、栈)leetcode 84. Largest Rectangle in Histogram, 85. Maximal Rectangle
    tmux 常见命令汇总
    leetcode 221
    leetcode 319 29
    (贪心)leetcode 392. Is Subsequence, 771. Jewels and Stones, 463. Island Perimeter
    leetcode 982 668
    Python import 同文件夹下的py文件的函数,pycharm报错
    Windows里Anaconda-Navigator无法打开的解决方案
    Windows下 gpu版 Tensorflow 安装
  • 原文地址:https://www.cnblogs.com/chris-jichen/p/10154316.html
Copyright © 2011-2022 走看看