精华内容
下载资源
问答
  • JavaScript异步编程》PDF版本下载
  • 今天研究一个小问题: 怎么拿到JavaScript异步函数的返回值? 1.错误尝试 当年未入行时,我的最初尝试: [removed] function getSomething() { var r = 0; setTimeout(function() { r = 2; }, 10); return r; ...
  • 在做项目的时候,有时候需要用到异步数据请求,但是如果这个时候没有框架的依赖,就需要用到原生JS进行异步数据请求。这时候无非有两种请求方式,一种是AJAX,另一个是JSONP。通过原生JS异步请求进行简单的封装。 ...
  • 前言 javascript是一门单线程的语言,也就是说一次只能完成一件任务,如果有多个任务,就需要排队进行处理。...目前,在javascript异步处理中,有以下几种方式: callback 回调函数是最早解决异步编
  • 本文为大家讲解下js异步操作时回调函数如何控制执行顺序,感兴趣的朋友可以参考下
  • 本文实例为大家分享了turn.js异步加载实现翻书效果的具体代码,供大家参考,具体内容如下 1、阅读翻书js /** * 电子翻书 */ //var width = 1080; //var height = 1680; var width = "10rem"; var height = "15.2...
  • 不同函数达到同步的函数模拟 funcList是函数执行函数的队列,其中回调函数中flag=true是同步标记量 [removed] var flag = false; function funcTest(t,func){ setTimeout(function(){ (function(param){ ...
  • 主要介绍了Javascript异步执行不按顺序解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • JavaScript实现异步的五种实现方法 文章目录JavaScript实现异步的五种实现方法前言一、同步和异步是什么?1.同步任务:2.异步任务:二、异步方法有哪些(5种):1.webWork(创建分线程)2.promise(es6方法)3.Async await ...

    JavaScript实现异步的五种实现方法



    前言

    一、同步和异步是什么?

    1.同步任务:

    js中的同步任务就是按照顺序执行(可以理解为A------>B,只有一条道路可以到达B)

    2.异步任务:

    异步肯定与同步不一样.简单来说异步任务就像:到达终点的路多了几条(A地点------->B地点,中间有很多条路可以走.不会造成A----->B地点的主路压力增大)
    异步任务 setTimeout setInterval xmlHttprequest 等

    二、异步方法有哪些(5种):

     	1.webwork 为了让任务在多线程去执行,防止复杂逻辑阻塞线程
        2.promise 为了让异步任务顺序执行,解决地狱回调
        3.window.fetch H5新增的拉取资源的 api  1.通过.then() 2.数据序列化 3.通过.then()获取数据
        4.jquery.deffered对象 为了让创建的deffered对象的时候和异步任务属于同步代码的结果,并且能够保证deffered和promise进行转换
        5.async和awit 实质上是对Promise()对象提供的 -语法糖-. 让代码更简洁.通过同步代码的形式来实现异步操作
    
    

    1.webWork(创建分线程)

    index.js为加载到html页面中的主线程(js文件)

    work.js为在index中创建的分线程


     1.werbwork的实现方法
        ①在html中引入index.js主线程
        ②在index.js中 创建分线程  var w =new webwork('work.js')
        ③在index.js中 通过 w.postmesage('数据') 向子线程发送数据
        ④在work.js中  通过onmessage=function(ev){ev.data  postmessage(a)} 接受主线程发送过来的ev.data数据
        ⑤在index.js中 通过onmessage=function(ev){ev.data} ev.data 接受  a 的值.
    

    index.js(主)代码如下(示例):

    //创建一个分线程 work.js
    var w = new Worker('work.js')
    
    //主线程向分线程发送数据
    w.postMessage(40);
    
    //接受分线程转递过来的数据
    w.onmessage = function(ev) {
        console.log(ev);
        console.log(ev.data);
    }
    

    work.js(分线程)代码如下(示例):

    function fibo(n) {
        var num = 1;
        var num1 = 1;
        var sum = 0;
        for (let i = 2; i < n; i++) {
            sum = num + num1;
            num = num1;
            num1 = sum;
    
        }
        return sum;
    }
    
    onmessage = function(ev) {
        console.log(ev);
        this.postMessage(fibo(ev.data))
    }
    

    2.promise(es6方法)

    介绍步骤 如下:

    2.promise的实现方法(处理异步),解决地狱回调
        ①创建promise实例   var p =new promise((reslove,reject)=>{reslove('res')});
        ②p.then(res=>  console.log(res))// reslove(变量) 对应的是 then(变量 => {变量的操作})
    
    
    	//promise 是对异步任务处理的对象,判断异步任务有没有成功执行的状态
        //resolve , reject 也是两个参数
        //resolve 是调用成功的函数  reject 是调用失败的函数
        //如果不使用 resove 和 reject 两个函数 状态为pendding ---resolve之后变成了 成功状态 fulfilled
        //promise 的then()  在成功状态的时候被触发  resolve(res)  res可以传递参数
        //promise 的catch()  在失败状态的时候被触发 reject(res)  res可以传递参数
        
    

    代码如下(示例):

     var promise = new Promise((reslove, reject) => {
            setTimeout(() => {
                console.log('执行大量代码');
            }, 2000);
            //resolve('我成功了'); //resolve 之后变成了 成功状态 fulfilled
            // reject('我失败了'); //reject 之后promise状态变为 失败状态
        });
        //promise 的then()  在成功状态的时候被触发  resolve(res)  res可以传递参数
        //promise 的catch()  在失败状态的时候被吃法 reject(err)  err可以传递参数
        promise.then((res) => {
            console.log(res);
        }).catch((err) => {
            console.log(err);
        })
    

    3.Async await 异步的实现方法

    介绍步骤 如下(相当于promise用法的语法糖):

    //async 和 await 的使用场景
        //async 与 await 必须连用 , await只能用在async函数里面
        //await作用等待异步任务完成,如果请求数据成功了,执行then函数
        //  async function getData() {
        //      console.log('000');
        //      var book1 = await window.fetch('https://autumnfish.cn/top/artists');
        //      //第二个请求
        //      console.log('1111');
        //      var book2 = await $.get('https://autumnfish.cn/top/artists');
        //      console.log('2222');
        //      return [book1.json(), book2]
        //  };
        //  console.log('aaa');
        //  getData().then(res => {
        //      return res[0];
        //  }).then(res => console.log(res)).catch(err => {
        //      console.log(err)
        //  })
    

    4.window.fetch() 的实现方法

      <script>
        //拉取资源 fetch是 es6新增的一个解决异步的方案, 拉取网络资源
    
        var url = 'http://123.207.32.32:8000/home/data?type=new&page1';
        //fetch() 返回响应的promise 
        // 第一层then返回的是:响应的报文对象   
        //第二层:如果想要使用第一层的数据,必须把数据序列化  res.json() 返回的对象是一个 Promise对象再进行then()
    
        window.fetch(url).then(res => {
            console.log(res);
            //将数据序列化
            return res.json()
        }).then(res => console.log(res));
    </script>
    

    5.jquery中的deffered对象 的实现方法

    <script>
        function cook() {
            console.log('开始做饭');
            var def = $.Deferred() //创建一个jquery延时对象
            setTimeout(() => {
                console.log('做完了');
                /*   def.resolve('烩面'); */
                def.reject('做饭失败')
            }, 2000);
            //返回Deferred()  吧def实例转化成promise实例返回
            //返回可以利用then catch finally 等这些函数
            return def.promise()
        }
        cook().then(res => {
            console.log(res);
        }).catch(err => {
            console.log(err);
        });
    
        /*    $.get('http://123.207.32.32:8000/home/data?type=new&page1').then(res => {
               console.log(res);
           }).catch(err => console.log(err)); */
    
        $.get('http://123.207.32.32:8000/home/data?type=new&page1').done(res => {
            console.log(res);
        }).fail(err => console.log(err)).then(() => alert(1213))
    </script>
    

    总结

    提示:这里对文章进行总结:

    异步处理方法

    1.webwork 为了让任务在多线程去执行,防止复杂逻辑阻塞线程
    2.promise 为了让异步任务顺序执行,解决地狱回调
    3.window.fetch H5新增的拉取资源的 api  1.通过.then() 2.数据序列化 3.通过.then()获取数据
    4.jquery.deffered对象 为了让创建的deffered对象的时候和异步任务属于同步代码的结果,并且能够保证deffered和promise进行转换
    5.async和awit 实质上是对Promise()对象提供的 -语法糖-. 让代码更简洁.通过同步代码的形式来实现异步操作
    
    
    1.werbwork的实现方法
    ①在html中引入index.js主线程
    ②在index.js中 创建分线程  var w =new webwork('work.js')
    ③在index.js中 通过 w.postmesage('数据') 向子线程发送数据
    ④在work.js中  通过onmessage=function(ev){ev.data  postmessage(a)} 接受主线程发送过来的ev.data数据
    ⑤在index.js中 通过onmessage=function(ev){ev.data} ev.data 接受  a 的值.
    
    2.promise的实现方法(处理异步),解决地狱回调
    ①创建promise实例   var p =new promise((reslove,reject)=>{reslove('res')});
    ②p.then(res=>  console.log(res))
    ③  // reslove(变量) 对应的是 then(变量 => {变量的操作})
    
    3.window.fetch() 的实现方法
    ①window.fetch('url').then(
         console.log(res)
         return res.json() //必须序列化  返回的是一个respones对象
    )
    4.async 和 awit的使用方法:
     async function f2() {
        var books = await $.get('https://autumnfish.cn/top/artists');
        return books
    };
    f2().then(res => {
        console.log(res);
    }) 
    5.jquery的jquery.deffered对象上面有介绍,就不做总结了
    
    展开全文
  • 什么是JavaScript异步编程?

    千次阅读 多人点赞 2019-05-28 23:08:57
    在我们的工作和学习当中,到处充满了异步的身影,到底什么是异步,什 么是异步编程,为什么要用异步编程,以及经典的异步编程有哪些,在工 作中的场景又有什么,我们一点点深入的去学习。 什么是异步编程? 有必要...

    在我们的工作和学习当中,到处充满了异步的身影,到底什么是异步,什 么是异步编程,为什么要用异步编程,以及经典的异步编程有哪些,在工 作中的场景又有什么,我们一点点深入的去学习。

    什么是异步编程?

    有必要了解一下,什么是异步编程,为什么要异步编程。 先说一个概念异步与同步。

    介绍异步之前,回顾一下,所谓同步编程,就 是计算机一行一行按顺序依次执行代码,当前代码任务耗时执行会阻塞后 续代码的执行。 同步编程,即是一种典型的请求-响应模型,当请求调用一个函数或方法 后,需等待其响应返回,然后执行后续代码。 一般情况下,同步编程,代码按序依次执行,能很好的保证程序的执行, 但是在某些场景下,比如读取文件内容,或请求服务器接口数据,需要根 据返回的数据内容执行后续操作,读取文件和请求接口直到数据返回这一 过程是需要时间的,网络越差,耗费时间越长,如果按照同步编程方式实 现,在等待数据返回这段时间,JavaScript 是不能处理其他任务的,此时 页面的交互,滚动等任何操作也都会被阻塞,这显然是及其不友好,不可 接受的,而这正是需要异步编程大显身手的场景。我们想通过 Ajax 请求数 据来渲染页面,这是一个在我们前端当中很常见渲染页面的方式。基本每 个页面都会都这样的过程。在这里用同步的方式请求页面会怎么样?浏览 器锁死,不能进行其他操作。而且每当发送新的请求,浏览器都会锁死, 用户体验极差。

    在浏览器中同步执行将会是上面的这个样子, 任务 1 做完才能做任务 2, 任务 2 做完才会做任务 3。这里面体现出同步编程的有序的特点。只能 1, 2,3 不能 1,3,2。但是我们的代码逻辑中可以存在多任务同时执行的过 程。在我们生活中,煮饭和烧水可以同时去做,同样在我们编程中也需要 这样的逻辑。

    在计算机中有多线程的概念,什么意思呢,每一个线程做一件事,像下面 任务 1 任务 2 任务 3 这样。

    在不同的线程中可以执行不同的任务。 但是我们的 JavaScript 是单线程的,这里的单线程,强调的执行线程是单 线程。后面也是有线程池的,线程以及线程池的概念在这里就不多说了。 想了解的同学可以看看操作系统相关书籍。

    JavaScript 语言执行环境是单线程的,单线程在程序执行时,所走的程序 路径按照连续顺序排下来,前面的必须处理好,后面的才会执行。 但是我们也需要类似多线程机制的这种执行方式。但是 JavaScript 还是单 线程的,我们需要异步执行,异步执行会使得多个任务并发执行。 并行与并发。前文提到多线程的任务可以并行执行,而 JavaScript 单线程 异步编程可以实现多任务并发执行,这里有必要说明一下并行与并发的区 别。 并行,指同一时刻内多任务同时进行。边煮饭,边烧水,可以同时进行 并发,指在同一时间段内,多任务同时进行着,但是某一时刻,只有某一 任务执行。边吃饭边喝水,同一时间点只能喝水和吃饭。 接下来说一说异步机制。

    并发模型

    目前,我们已经知道,JavaScript 执行异步任务时,不需要等待响应返回, 可以继续执行其他任务,而在响应返回时,会得到通知,执行回调或事件 处理程序。那么这一切具体是如何完成的,又以什么规则或顺序运作呢? 接下来我们需要解答这个问题。回调和事件处理程序本质上并无区别,只 是在不同情况下,不同的叫法。 前文已经提到,JavaScript 异步编程使得多个任务可以并发执行,而实现 这一功能的基础是 JavaScript 拥有一个基于事件循环的并发模型。

    堆栈与队列

    介绍 JavaScript 并发模型之前,先简单介绍堆栈和队列的区别:

    堆(heap):内存中某一未被阻止的区域,通常存储对象(引用类型);

    栈(stack):后进先出的顺序存储数据结构,通常存储函数参数和基本类 型值变量(按值访问);

    队列(queue):先进先出顺序存储数据结构。

    事件循环(Event Loop)

    JavaScript 引擎负责解析,执行 JavaScript 代码,但它并不能单独运行,通 常都得有一个宿主环境,一般如浏览器或 Node 服务器,前文说到的单线 程是指在这些宿主环境创建单一线程,提供一种机制,调用 JavaScript 引 擎完成多个 JavaScript 代码块的调度,执行(是的,JavaScript 代码都是按 块执行的),这种机制就称为事件循环(Event Loop)。 JavaScript 执行环境中存在的两个结构需要了解:

    消息队列(message queue),也叫任务队列(task queue):存储待处理消 息及对应的回调函数或事件处理程序; 执行栈(execution context stack),也可以叫执行上下文栈:JavaScript 执行 栈,顾名思义,是由执行上下文组成,当函数调用时,创建并插入一个执 行上下文,通常称为执行栈帧(frame),存储着函数参数和局部变量, 当该函数执行结束时,弹出该执行栈帧; 注:关于全局代码,由于所有的代码都是在全局上下文执行,所以执行栈 顶总是全局上下文就很容易理解,直到所有代码执行完毕,全局上下文退 出执行栈,栈清空了;也即是全局上下文是第一个入栈,最后一个出栈。

    任务:分析事件循环流程前,先阐述两个概念,有助于理解事件循环:同步任务 和异步任务。 任务很好理解,JavaScript 代码执行就是在完成任务,所谓任务就是一个 函数或一个代码块,通常以功能或目的划分,比如完成一次加法计算,完 成一次 ajax 请求;很自然的就分为同步任务和异步任务。同步任务是连续 的,阻塞的;而异步任务则是不连续,非阻塞的,包含异步事件及其回调, 当我们谈及执行异步任务时,通常指执行其回调函数。

    事件循环流程 关于事件循环流程分解如下:

    • 宿主环境为 JavaScript 创建线程时,会创建堆(heap)和栈(stack),堆内存储 JavaScript 对象,栈内存储执行上下文;
    • 栈内执行上下文的同步任务按序执行,执行完即退栈,而当异步任务执行 时,该异步任务进入等待状态(不入栈),同时通知线程:当触发该事件 时(或该异步操作响应返回时),需向消息队列插入一个事件消息;
    • 当事件触发或响应返回时,线程向消息队列插入该事件消息(包含事件及 回调);
    • 当栈内同步任务执行完毕后,线程从消息队列取出一个事件消息,其对应 异步任务(函数)入栈,执行回调函数,如果未绑定回调,这个消息会被 丢弃,执行完任务后退栈;
    • 当线程空闲(即执行栈清空)时继续拉取消息队列下一轮消息(next tick, 事件循环流转一次称为一次 tick)。

    使用代码可以描述如下:

    var eventLoop = [];
    var event;
    var i = eventLoop.length - 1; // 后进先出
    while(eventLoop[i]) {
        event = eventLoop[i--];
        if (event) { // 事件回调存在
            event();
        }
        // 否则事件消息被丢弃
    }
    

    这里注意的一点是等待下一个事件消息的过程是同步的。

    var ele = document.querySelector('body');
    function clickCb(event) {
        console.log('clicked');
    }
    function bindEvent(callback) {
        ele.addEventListener('click', callback);
    } 
    bindEvent(clickCb);

    针对如上代码我们可以构建如下并发模型:

    如上图,当执行栈同步代码块依次执行完直到遇见异步任务时,异步任务 进入等待状态,通知线程,异步事件触发时,往消息队列插入一条事件消 息;而当执行栈后续同步代码执行完后,读取消息队列,得到一条消息, 然后将该消息对应的异步任务入栈,执行回调函数;一次事件循环就完成 了,也即处理了一个异步任务。

    JS 中异步有几种?

    JS 中异步操作还挺多的,常见的分以下几种:

    • setTimeout (setInterval)
    • AJAX
    • Promise
    • Generator

    setTimeout (setInterval)

    setTimeout(function() {
        console.log("Hello!");
    }, 1000);

    setTimout(setInterval)并不是立即就执行的,这段代码意思是,等 1s 后,把这个 function 加入任务队列中,如果任务队列中没有其他任务了, 就执行输出 'Hello'。

    var outerScopeVar;
    helloCatAsync();
    alert(outerScopeVar);
    function helloCatAsync() {
        setTimeout(function() {
            outerScopeVar = 'hello';
        }, 2000);
    }

    执行上面代码,发现 outerScopeVar 输出是 undefined,而不是 hello。之 所以这样是因为在异步代码中返回的一个值是不可能给同步流程中使用 的,因为 console.log(outerScopeVar) 是同步代码,执行完后才会执行 setTimout。

    helloCatAsync(function(result) {
        console.log(result);
    });
    function helloCatAsync(callback) {
        setTimeout(function() {
            callback('hello')
        }, 1000)
    }
    

    把上面代码改成,传递一个 callback,console 输出就会是 hello。

    AJAX

    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 ) {
            console.log(xhr.responseText);
        } else {
            console.log( xhr.status);
        }
    }
    xhr.open('GET', 'url', false);
    xhr.send();
    

    上面这段代码,xhr.open 中第三个参数默认为 false 异步执行,改为 true 时为同步执行。

    Promise

    规范简述:

    promise 是一个拥有 then 方法的对象或函数。

    一个 promise 有三种状态 pending, rejected, resolved 状态一旦确定就不能 改变,且只能够由 pending 状态变成 rejected 或者 resolved 状态,reject 和 resolved 状态不能相互转换。 当 promise 执行成功时,调用 then 方法的第一个回调函数,失败时调用第 二个回调函数。 promise 实例会有一个 then 方法,这个 then 方法必须返回一个新的 promise。 基本用法:

    // 异步操作放在 Promise 构造器中
    const promise1 = new Promise((resolve) => {
        setTimeout(() => {
            resolve('hello');
        }, 1000);
    });
    // 得到异步结果之后的操作
    promise1.then(value => {
        console.log(value, 'world');
    }, error =>{
        console.log(error, 'unhappy')
    });
    

    异步代码,同步写法:

    asyncFun()
        .then(cb)
        .then(cb)
        .then(cb)

    promise 以这种链式写法,解决了回调函数处理多重异步嵌套带来的回调 地狱问题,使代码更加利于阅读,当然本质还是使用回调函数。

    异常捕获

    前面说过如果在异步的 callback 函数中也有一个异常,那么是捕获不到的, 原因就是回调函数是异步执行的。我们看看 promise 是怎么解决这个问题 的。

    asyncFun(1).then(function (value) {
        throw new Error('出错啦');
    }, function (value) {
        console.error(value);
    }).then(function (value) {}, function (result) {
        console.log('有错误', result);
    });

    其实是 promise 的 then 方法中,已经自动帮我们 try catch 了这个回调函数, 实现大致如下。

    Promise.prototype.then = function(cb) {
        try {
            cb()
        } catch (e) {
            // todo
            reject(e)    
        }
    }

    then 方法中抛出的异常会被下一个级联的 then 方法的第二个参数捕获到 (前提是有),那么如果最后一个 then 中也有异常怎么办。

    Promise.prototype.done = function (resolve, reject) {
        this.then(resolve, reject).catch(function (reason) {
            setTimeout(() => {
                throw reason;
            }, 0);
        });
    };
    asyncFun(1).then(function (value) {
        throw new Error('then resolve 回调出错啦');
    }).catch(function (error) {
        console.error(error);
        throw new Error('catch 回调出错啦');
    }).done((reslove, reject) => {});

    我们可以加一个 done 方法,这个方法并不会返回 promise 对象,所以在 此之后并不能级联,done 方法最后会把异常抛到全局,这样就可以被全 局的异常处理函数捕获或者中断线程。这也是 promise 的一种最佳实践策 略,当然这个 done 方法并没有被 ES6 实现,所以我们在不适用第三方 Promise 开源库的情况下就只能自己来实现了。为什么需要这个 done 方 法。

    const asyncFun = function (value) {
        return new Promise(function (resolve, reject) {
            setTimeout(function () {
                resolve(value);
            }, 0);
        })
    };
    asyncFun(1).then(function (value) {
        throw new Error('then resolve 回调出错啦');
    });
    
    (node:6312) UnhandledPromiseRejectionWarning: Unhandled promise
    rejection (rejection id: 1): Error: then resolve 回调出错啦
    (node:6312) [DEP0018] DeprecationWarning: Unhandled promise rejections
    are deprecated. In the future, promise rejections that are not handled will
    terminate the Node.js process with a non-zero exit code

    局限

    promise 有一个局限就是不能够中止 promise 链,例如当 promise 链中某一 个环节出现错误之后,已经没有了继续往下执行的必要性,但是 promise 并没有提供原生的取消的方式,我们可以看到即使在前面已经抛出异常, 但是 promise 链并不会停止。虽然我们可以利用返回一个处于 pending 状 态的 promise 来中止 promise 链。

    const promise1 = new Promise((resolve) => {
        setTimeout(() => {
            resolve('hello');
        }, 1000);
    });
    promise1.then((value) => {
        throw new Error('出错啦!');
    }).then(value => {
        console.log(value);
    }, error=> {
        console.log(error.message);
        return result;
    }).then(function () {
        console.log('yinchengnuo');
    });
    

    上面所说的都是 ES6 的 promise 实现,实际上功能是比较少,而且还有一 些不足的,所以还有很多开源 promise 的实现库,像 q.js 等等,它们提供 了更多的语法糖,也有了更多的适应场景。

    核心代码

    var defer = function () {
        var pending = [], value;
        return {
            resolve: function (_value) {
                value = _value;
                for (var i = 0, ii = pending.length; i < ii; i++) {
                    var callback = pending[i];
                    callback(value);
                }
                pending = undefined;
            },
            then: function (callback) {
                if (pending) {
                    pending.push(callback);
                } else {
                    callback(value);
                }
            }
        }
    };

    当调用 then 的时候,把所有的回调函数存在一个队列中,当调用 resolve 方法后,依次将队列中的回调函数取出来执行:

    var ref = function (value) {
        if (value && typeof value.then === "function")
            return value;
        return {
            then: function (callback) {
                return ref(callback(value));
            }
        };
    };

    这一段代码实现的级联的功能,采用了递归。如果传递的是一个 promise 那么就会直接返回这个 promise,但是如果传递的是一个值,那么会将这 个值包装成一个 promise。 下面 promise 和 ajax 结合例子:

    function ajax(url) {
        return new Promise(function(resolve, reject) {
            var xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function() {
                if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 ) {
                    resovle(xhr.responseText);
                } else {
                    reject( xhr.status);
                }
            }
            xhr.open('GET', url, false);
            xhr.send();
        });
    }
    ajax('/test.json')
        .then(function(data){
            console.log(data);
        })
        .cacth(function(err){
            console.log(err);
        });

    generator

    基本用法:

    function * gen (x) {
        const y = yield x + 2;
        // console.log(y); // 猜猜会打印出什么值
    }
    const g = gen(1);
    console.log('first', g.next()); //first { value: 3, done: false }
    console.log('second', g.next()); // second { value: undefined, done: true }

    通俗的理解一下就是 yield 关键字会交出函数的执行权,next 方法会交回 执行权,yield 会把 generator 中 yield 后面的执行结果,带到函数外面,而 next 方法会把外面的数据返回给 generator 中 yield 左边的变量。这样就实 现了数据的双向流动。我们来看 generator 如何是如何来实现一个异步编程(*)

    const fs = require('fs');
    function * gen() {
        try {
            const file = yield fs.readFile;
            console.log(file.toString());
        } catch(e) {
            console.log('捕获到异常', e);
        }
    }
    // 执行器
    const g = gen();
    g.next().value('./config1.json', function (error, value) {
        if (error) {
            g.throw('文件不存在');
        }
        g.next(value);
    });

    那么我们 next 中的参数就会是上一个 yield 函数的返回结果,可以看到在 generator 函数中的代码感觉是同步的,但是要想执行这个看似同步的代 码,过程却很复杂,也就是流程管理很复杂。

    扩展:异步编程与多线程的区别

    共同点:异步和多线程两者都可以达到避免调用线程阻塞的目的,从而提 高软件的可响应性

    不同点:

    (1)线程不是一个计算机硬件的功能,而是操作系统提供的一种逻辑功能,线程本质上是进程中一段并发运行的代码,所以线程需要操作 系统投入 CPU 资源来运行和调度。 多线程的优点很明显,线程中的处理程序依然是顺序执行, 符合普通人的思维习惯,所以编程简单。但是多线程的缺点也同样明显, 线 程的使用(滥用)会给系统带来上下文切换的额外负担。并 且线程间的共享变量可能造成死锁的出现

    (2)异步操作无须额外的线程负担,并且使用回调的方式进行处理, 在设计良好的情况下,处理函数可以不必使用共享变量(即使无法 完 全不用,最起码可以减少 共享变量的数量),减少了死 锁的可能。当然异步操作也并非完美无暇。编写异步操作的复杂程度较高, 程序 主要使用回调方式进行处理,与普通人的思维方式有些 初入,而且难以调试。

    这里有一个疑问。异步操作没有创建新的线程,我们一定会想,比 如有一个文件操作,大量数据从硬盘上读取,若使用单线程的同步操作自 然要等待会很长时间,但是若使用异步操作的话,我们让数据读取异步进 行,二线程在数据读取期间去干其他的事情,我们会想,这怎么行呢,异 步没有创建其他的线程,一个线程去干其他的事情去了,那数据的读取异 步执行是去由谁完成的呢?实际上,本质是这样的。

    熟悉电脑硬件的朋友肯定对 DMA 这个词不陌生,硬盘、光驱的技 术规格中都有明确 DMA 的模式指标,其实网卡、声卡、显卡也是有 DMA 功能的。DMA 就是直 接内存访问的意思,也就是说,拥有 DMA 功能的 硬件在和内存进行数据交换的时候可以不消耗 CPU 资源。只要 CPU 在发 起数据传输时发送一个指令,硬件就开 始自己和内存交换数据,在传输 完成之后硬件会触发一个中断来通知操作完成。这些无须消耗 CPU 时间的 I/O 操作正是异步操作的硬件基础。所以即使在 DOS 这样的单进程(而且 无线程概念)系统中也同样可以发起异步的 DMA 操作。

    即 CPU 在数据的长时间读取过程中 ,只需要做两件事,第一发布指 令,开始数据交换;第二,交换结束,得到指令,CPU 再进行后续操作。 而中间读取数据漫长的等待过程,CPU 本身就不需要参与,顺序执行就是 我不参与但是我要干等着,效率低下;异步执行就是,我不需要参与那我 就去干其他事情去了,你做完了再通知我就可以了(回调)。

    但是你想一下,如果有一些异步操作必须要 CPU 的参与才能完成呢, 即我开始的那个线程是走不开的,这该怎么办呢,在.NET 中,有线程池去 完成,线程池会高效率的开启一个新的线程去完成异步操作,在 python 中这是系统自己去安排的,无需人工干预,这就比自己创建很多的线程更 加高效。

    总结:

    (1)“多线程”,第一、最大的问题在于线程本身的调度和运行需 要很多时间,因此不建议自己创建太大量的线程;第二、共享资源的调度 比较难,涉及到死锁,上锁等相关的概念。

    (2)“异步” ,异步最大的问题在于“回调”,这增加了软件设 计上的难度。 在实际设计时,我们可以将两者结合起来:

                    (1)当需要执行 I/O 操作时,使用异步操作比使用线程+同步 I/O 操作更合适。I/O 操作不仅包括了直接的文件、网络的读写,还包括数据 库操作、Web Service、HttpRequest 以及.net Remoting 等跨进程的调用。 异步特别适用于大多数 IO 密集型的应用程序。

                    (2)而线程的适用范围则是那种需要长时间 CPU 运算的场合,例如 耗时较长的图形处理和算法执行。但是往 往由于使用线程编程的简单和 符合习惯,所以很多朋友往往会使用线程来执行耗时较长的 I/O 操作。这 样在只有少数几个并发操作的时候还无伤大雅,如果需要处 理大量的并 发操作时就不合适了。

    展开全文
  • 本文主要介绍了JS实现异步文件上传的相关资料,具有很好的参考价值。下面跟着小编一起来看下吧
  • 1) 当点击Load的时候,模拟执行异步加载. 浏览器被遮挡. 进度条出现. 实现思路: 1.当用户点击load button执行异步请求. 调用方法 出现加载条 2.怎么实现进度条呢? 1) 在document.body 新增一个div.覆盖浏览器. ...
  • JavaScript异步编程.pdf

    2017-06-20 21:04:57
    JavaScript异步编程.pdf 个人收集电子书,仅用学习使用,不可用于商业用途,如有版权问题,请联系删除!
  • Javascript异步编程的4种方法
  • javascript异步操作同步化

    千次阅读 2020-01-09 11:22:01
    javascript中,比如我们在请求数据的时候,举个例子,查询所有的文章列表, 接着拿到文章列表再去查询文章详情.这是两个请求,但是查询文章详情必然要拿到该文章的id,也就是查询文章详情的接口,需要在查询文章列表后来...

        在javascript中,比如我们在请求数据的时候,举个例子,查询所有的文章列表, 接着拿到文章列表再去查询文章详情.这是两个请求,但是查询文章详情必然要拿到该文章的id,也就是查询文章详情的接口,需要在查询文章列表后来执行,否则就会拿不到文章的id.那如何保证先查询文章列表,再查文章详情呢????

         这就是异步操作的同步化, 以下是解决方案:

                若查询文章列表的函数是findAll()

                查询文章详情的函数是findById()

         1. 回调函数嵌套     

       
    
           findAll().then((res)=>{
                
                findById()
    
            })

              这种方式如果是简单的业务逻辑可以这样使用,但是若是逻辑复杂就需要多层回调,也就是所谓的回调噩梦,所以不是特别可取


          2.Promise...then方式
             promise的流程如下图所示:

    Promise一旦新建就立刻执行, 此时的状态是Pending(进行中),它接受两个参数分别是resolve和reject.它们是两个函数.
    resolve函数的作用是将Promise对象的状态从'未完成'变为'成功'(由Pending变为Resolved), 在异步操作成功时,将操作结果作为参数传递出去;
    reject函数的作用是将Promise对象的状态从'未完成'变为失败(由Pending变为Rejected),在异步操作失败时调用,并将异步操作的错误作为参数传递出去.

    <script type="text/javascript">
    		function foo(){
    			    return new Promise((reslove,reject)=>{
    			    	findAll().then(()=>{
    			    		 reslove();
    			    		}).catch(()=>{
    			    			reject();
    			    		})
    			    })
    			}
    		foo.then((reslove)=>{
    			findById()
    		})
    	</script>

     3.generator函数  /  async函数

       具体用法,见上一篇

    链接https://blog.csdn.net/rolinabc/article/details/103902759

    展开全文
  • 主要介绍了javascript异步编程的六种方式总结,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • javascript 异步编程的5种方式

    千次阅读 2018-10-17 13:13:02
    javascript中有分同步代码,和异步代码,顾名思义,同步代码,就是依此执行的代码,异步代码可能不会立即执行,得等到某一特定事件触发时才会执行,javascript有个任务队列,用来存放异步代码,任务队列中的任务又...


    前言

    javascript是单线程的一门语言,所以在执行任务的时候,所有任务必须排队,然后一个一个的执行,
    在javascript中有分同步代码,和异步代码,顾名思义,同步代码,就是依此执行的代码,异步代码可能不会立即执行,得等到某一特定事件触发时才会执行,javascript有个任务队列,用来存放异步代码,任务队列中的任务又有优先级之分,微任务(microtask)的优先级大于宏任务(macrotask),在javascript中代码的执行顺序为,主线程会先执行完同步代码,并将异步代码放到任务队列中,当同步代码执行完毕,就轮询任务队列,先询问微任务,如果有则执行微任务,如果没有询问宏任务。

     

     

    //异步代码
    setTimeout(function () { //属于宏任务
       console.log('hello world3');
    },0);
    new Promise(resolve => { //属于微任务
      console.log('hello world4'); //Promise 对象会立即执行 所以new Promise里面的类似与同步代码
      resolve('hello world5');
    }).then(data => {console.log(data)});
    
    //同步代码
    function main(){
      console.log('hello world');
    }
    console.log('hello world1');
    console.log('hello world2');
    main();



    输出结果为:

        hello world4
        hello world1
        hello world2
        hello world
        hello world5
        hello world3

    按照上面所说的顺序,同步代码先执行,那么会先输出hello world4 然后hello world1 ,hello world2,hello world 接下来执行任务队列的异步代码,先轮询微任务是否有要执行的代码,由于Promise对象属于微任务的,故先执行它,输出hello world5 ,然后执行宏任务的代码,及setTimeout的代码,输出hello world3

    本例比较简单,讲述了一下javascript代码的执行流程,希望对理解异步有帮助,其中涉及的Promise对象会在本文详细介绍。

    本文代码可能比较多,所有涉及的代码均在我的github上 

    https://github.com/sundial-dreams/javascript_async

    接下来回归正题,Javascript中异步的5种实现方法,并以ajax等为例子,实现几种异步的编写方式


    javascript中的异步实现方式有以下几种

    1.  callback (回调函数)

    2.  发布订阅模式

    3. Promise对象

    4. es6的生成器函数

    5.  async/await

     

    1.callback (回调函数)

     回调函数是Javascript异步编程中最常见的,由于JavaScript中的函数是一等公民,可以将其以参数形式传递,故就有了回调函数一说,熟悉nodejs的人知到,里面涉及非常多的回调,这些回调代表着,当某个任务处理完,然后需要做的事,比如读取文件,连接数据库,等文件准备好,或数据库连接成功执行编写的回调函数,又比如像一些动画处理,当动画走完,然后执行回调,举个例子
     

     function load(url,callback){
        //something
        setTimeout(callback,3000);//假设某个异步任务处理需要3s 3s后执行回调
    }
    
    load('xxx',function() {
        //do something
        console.log('hello world')
    })

     

     

    再来看个ajax例子 (代码 ) 

     

    //ajax_callback.js
    
    function ajax(object, callback) {
      function isFunction(func) { // 是否为函数
        return typeof func === 'function';
      }
    
      function isObject(object) { //是否为对象
        return typeof object === 'object';
      }
    
      function toQuerystring(data) { //对象转成查询字符串 例如{a:1,b:2} => a=1&b=2 或{a:[1,2],b:3} => a=1&a=2&b=3
        if (!isObject(data) || !data) throw new Error('data not object');
        var result = '';
        for (var key in data) {
          if (data.hasOwnProperty(key)) {
            if (isObject(data[key]) && !Array.isArray(data[key])) throw new Error('not support error');//除去对象
            if (Array.isArray(data[key])) { 
              data[key].forEach(function (v) {
                 result += key + '=' + v + '&'   
              });
            } else {
              result += key + '=' + data[key] + '&';
            }
          }
        }
        return result.substr(0, result.length - 1);//去掉末尾的&
      }
    
      var url = object.url || '';
      var method = object.method.toUpperCase() || 'GET';
      var data = object.data || Object.create(null);
      var async = object.async || true;
      var dataType = object.dataType || 'json';//相应的数据类型 可选json ,text, xml
    
      
      var xhr = new XMLHttpRequest();
      
      url = ajax.baseUrl + url;
      data = toQuerystring(data);
      method === 'GET' && (url += '?' + data) && (data = null); //get 请求 => url 后面加上 ?a=1&b=2这种
      
      try {
        xhr.open(method, url, async);
        method === 'POST' && (xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'));//post请求需要设置请求头为 application/x-www-form-urlencoded 类型
        console.log(data);
        xhr.send(data);
        xhr.onreadystatechange = function () {//监听事件
          if (this.readyState === 4) {
            if (this.status === 200)
              if (isFunction(callback))
                switch (dataType) {
                  case 'json': {
                    callback(JSON.parse(this.responseText));//完成时执行传进来的回调
                    break
                  }
                  case 'text': {
                    callback(this.responseText);//重点在这,ajax接到数据就执行传入的函数
                    break
                  }
                  case 'xml': {
                    callback(this.responseXML);
                    break
                  }
                  default: {
                    break;
                  }
                }
          }
        }
      } catch (e) {
        console.log(e);
      }
    }
    
    ajax.get = function (url, data, callback) { //get方法
      this({url: url, method: 'GET', data: data}, callback);
    };
    ajax.post = function (url, data, callback) { //post方法
      this({url: url, method: 'POST', data: data}, callback);
    };
    ajax.baseUrl = '';
    
    


    以上是个完整的ajax实例,当ajax完成执行回调
    以下是使用koa实现的一个简易的服务端,模拟处理ajax的响应,之后的例子都会用这个来模拟ajax响应
     

    //koa_test_server.js
    
    const Koa = require('koa');
    const Router = require('koa-router');
    const bodyparser = require('koa-bodyparser');
    const app = new Koa();
    const api = new Router();
    api.get('/api/test1', async ctx => { //处理get请求
      ctx.res.setHeader('Access-Control-Allow-Origin', '*');//允许跨域访问
      let querystring = ctx.querystring;
      console.log(querystring);
      ctx.body = JSON.stringify({
        errno: false,
        data: 'it is ok',
        message: `you send me ${querystring} type is GET`
      });
    }).post('/api/test2', async ctx => {//处理post请求
      ctx.res.setHeader('Access-Control-Allow-Origin', '*');//允许跨域访问
      let data = ctx.request.body;
      console.log(data);
      ctx.body = JSON.stringify({
        errno: false,
        data: 'it is ok',
        message: `you send me ${JSON.stringify(data)} type is POST`
      })
    });
    app.use(bodyparser());
    app.use(api.routes()).use(api.allowedMethods());
    app.listen(3000, () => {
      console.log('listen in port 3000')
    });
    
    



    简单使用如下
     

    //test.html
    
      ajax.baseUrl = 'http://localhost:3000';
      ajax.get('/api/test1',{name: 'dpf', age: 19},function (data) {
        //do something such as render page
        console.log(data);
      });
      ajax.post('/api/test2',{name: 'youname', age: 19}, function (data) {
        //do something such as render page
        console.log(data);
      });
    


    结果如下:

    回调的好处就是容易编写,缺点就是过多的回调会产生回调地狱,代码横向扩展,代码可读性变差
    不过回调还有很多应用,而且回调也是最常用的实现Javascript异步的方式。

     

     

    2.发布订阅模式

    发布订阅模式是设计模式的一种,并不是javascript特有的内容,所以javascript可以用发布订阅模式来做异步,那么其他语言如C++ java python php 等自然也能,其他语言实现的均在我的github里。

    简单介绍一下发布订阅模式,发布订阅是两个东西,即发布和订阅,想象一下,有家外卖,你可以点外卖,这就是订阅,当你的外卖做好了,就会有人给你打电话叫你去取外卖,这就是发布,简单来说,发布订阅模式,有一个事件池,用来给你订阅(注册)事件,当你订阅的事件发生时就会通知你,然后你就可以去处理此事件,模型如下


    接下来简单实现这个发布订阅模式

    //async_Event.js
    
    //单对象写法 Event 就相当于事件中心
    const Event = function () { //使用闭包的好处 : 把EventPool私有化,外界无法访问EventPool
      const EventPool = new Map();//使用es6 map来存 event,callback 键值对
      const isFunction = func => typeof func === 'function';
    
      const on = (event, callback) => { //注册事件
        EventPool.get(event) || EventPool.set(event, []);
        if (isFunction(callback)) {
          EventPool.get(event).push(callback);
        }
        else {
          throw new Error('callback not is function')
        }
      };
      const addEventListenr = (event, callback) => { //on方法别名
        on(event, callback)
      };
      const emit = (event, ...args) => { //触发(发布)事件
        //让事件的触发为一个异步的过程,即排在同步代码后执行
        //也可以setTimeout(fn,0)
        Promise.resolve().then(() => {
          let funcs = EventPool.get(event);
          if (funcs) {
            funcs.forEach(f => f(...args))
          } else {
            throw new Error(`${event} not register`)
          }
        })
      };
      const send = (event, ...args) => {//emit方法别名
        emit(event,...args)
      };
      const removeListener = event => {//删除事件
        Promise.resolve(() => {//删除事件也为异步的过程
          if(event){
            EventPool.delete(event)
          }else{
            throw new Error(`${event} not register`)
          }
        })
      };
    
      return {
        on, emit, addEventListenr, send
      }
    }();


    简单使用

     

     Event.on('event', data => {//注册事件,名为event
          console.log(data)
        });
    setTimeout(() => {
          Event.emit('event','hello wrold')
        },1000);
    
    //1s后触发事件,输出hello world


    使用发布订阅模式,修改之前的ajax例子
     

    //仅看这部分代码
    xhr.onreadystatechange = function () {//监听事件
          if (this.readyState === 4) {
            if (this.status === 200)
                switch (dataType) {
                  case 'json': {
                    Event.emit('data '+method,JSON.parse(this.responseText));//触发事件
                    break
                  }
                  case 'text': {
                    Event.emit('data '+method,this.responseText);
                    break
                  }
                  case 'xml': {
                    Event.emit('data '+method,this.responseXML);
                    break
                  }
                  default: {
                    break;
                  }
                }
             }
        }

    使用如下

    
    //test.html
    //注册事件
    Event.on('data GET',data => {
          //do something such as render page
          console.log(data)
        });
    
    Event.on('data POST',data => {
          //do something such as render page
          console.log(data)
        });
    //使用ajax    
    ajax.baseUrl = 'http://localhost:3000';
    ajax.get('/api/test1',{name: 'dpf', age: 19});
    ajax.post('/api/test2',{name: 'youname', age: 19});


     

    发布订阅模式是很重要的一种设计模式,在JavaScript中应用非常广泛,比如一些前端框架比如React,Vue等,都有使用这一设计模式,nodejs使用的就更多了。

    使用发布订阅模式的好处是事件集中管理,修改方便,缺点就是,代码可读性下降,事件容易冲突。

     

     

    3.Promise对象

    Promise对象是异步编程的一种解决方案,比传统的回调函数和事件更合理更强大。
    Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件的结果,相比回调函数,Promise提供统一的API,各种异步操作都可以用同样的方法进行处理。


    Promisel对象的两个特点:

    1.对象状态不受外界影响。

    Promise对象有三种状态:pending(进行中),fulfilled(已成功),rejected(已失败),当异步操作有结果时可以指定pending状态到fulfilled状态或pending状态到rejected状态的转换,状态一旦变为fulfilled,或rejected则这个Promise对象状态不会在改变。

    2.一旦状态改变,就不再变化,任何时候都可以得到这个结果。

     

    上面的看不懂也没关系,先把基本格式记住,然后多写写,也就理解了
     

    //基本格式
    let promise = new Promise((resolve, reject) => {//Promise对象接受一个函数
      try {
        setTimeout(() => {//模拟某异步操作 , 若操作成功返回数据 
          resolve('hello world'); //resolve() 使pending状态变为 fulfilled,需要注意resolve()函数最多只能接收1个参数,若要传多个参数,需要写成数组,或对象,比如resolve([1,2,2,2])或resolve({data,error})
          reject(); //状态已变为fulfilled 故下面这个reject()不执行
        }, 1000);
      }catch (e) {
        reject(e) //操作失败 返回Error对象 reject() 使pending状态变为rejected
      }
    });
    
    promise.then((data) => {
      console.log(data)   //resolve()函数里面传的值
    },(err) => {
      console.log(err) //reject()函数里传的值
    });
    
    //1s后输出hello world


    Promise对象的几个方法

    1. then(fulfilled,rejected)方法:

    异步任务完成时执行的方法,其中fulfilled(data)和rejected(err)分别是单参的回调函数,fulfilled对应的是成功时执行的回调,rejected对应的是失败时执行的回调,fulfilled函数的所接参数为resolve()函数传的值,rejected函数的参数则为reject()函数所传的值。

    2. catch(rejected)方法:

    then(null,rejected)的别名 捕获Promise对象中的错误

    3. Promise.resolve(data):

    等价于new Promise(resolve => {resolve(data)})

    4.Promise.all([promise1,promise2,...,promisen]):

    用于多个Promise对象的执行,执行时间取最慢的那个,例如:

     

    let promise1 = new Promise(resolve => {
      setTimeout(() => {
        resolve(1);
      }, 1000);
    });
    let promise2 = new Promise(resolve => {
      setTimeout(() => {
        resolve(2)
      }, 2000)
    });
    let promise3 = new Promise(resolve => {
      setTimeout(() => {
        resolve(3)
      }, 3000)
    });
    let start = Date.now();
    Promise.all([promise1, promise2, promise3]).then(([data1, data2, data3]) => {//使用数组解构获得每个Promise对象的data
      console.log(`datas = ${data1},${data2},${data3} total times = ${Date.now() - start}ms`);
    });
    
    //输出结果为 datas = 1,2,3 total times = 3000ms

    5.Promise.race([promise1,promise2,...,promisen]):

    和Promise.all类似,不过它取Promise对象中最快的那个。

    6.Promise.reject(err):

    等价于new Promise((resolve,reject) => reject(err))

    对有了Promise对象有了基本的理解,然后可以用它来替代回调函数的模式,比如一个图片加载例子
     

    //回调形式
    function asyncLoadImage_callback(url,callback) {//异步加载图片
      var proxyImage = new Image();//图片代理
      proxyImage.src = url;
      proxyImage.onload = callback;//加载完时执行回调
    }
    asyncLoadImage_callback('xxx', function () {
      image.src = 'xxx'//让真正的图片对象显示
    });
    
    //Promise对象形式
    function asyncLoadImage_Promise(url) {
      return new Promise((resolve,reject) => {
        var proxyImage = new Image();
        proxyImage.src = url;
        proxyImage.onload = resolve;
        proxyImage.onerror = reject;
      })
    }
    asyncLoadImage_Promise('xxx')
      .then(() => {
      image.src = 'xxx'//让真正的图片对象显示
     }).catch(err => console.log(err));
    


    使用Promise对象的好处比较明显,除了写起来有一些麻烦而已,不过设计Promise可不仅仅让你这么用用,替代回调函数就完事了的,在后面的生成器实现异步,及async/await中,Promise将发挥巨大作用。

    接下来将介绍将回调函数形式与Promise对象形式的相互转换,以下纯属个人兴趣,其实nodejs里的util包里面又promisify和callbackify两个函数,专门实现这个的。

    1.回调函数形式转换为Promise对象形式
     

    //promisify.js
    //callback => Promise
    /**
     *
     * @param fn_callback
     * @returns {function(...[*]): Promise<any>}
     */
    function promisify(fn_callback) { //接收一个有回调函数的函数,回调函数一般在最后一个参数
      if(typeof fn_callback !== 'function') throw new Error('The argument must be of type Function.');
      return function (...args) {//返回一个函数
        return new Promise((resolve, reject) => {//返回Promise对象
          try {
            if(args.length > fn_callback.length) reject(new Error('arguments too much.'));
            fn_callback.call(this,...args,function (...args) {
              args[0] && args[0] instanceof Error && reject(args[0]);//nodejs的回调,第一个参数为err, Error对象
              args = args.filter(v => v !== undefined && v !== null);//除去undefined,null参数
              resolve(args)
            }.bind(this));//保证this还是原来的this
          } catch (e) {
            reject(e)
          }
        })
      }
    }



    简单使用
     

    //nodejs的fs.readFile为例
    let asyncReadFile = promisify(require('fs').readFile);
    asyncReadFile('async.js').then(([data]) => {
      console.log(data.toString());
    }, err => console.log(err));
    
    //将上面的asyncLoadImage_callback转换为例
    let asyncLoadImage = promisify(asyncLoadImage_callback);
    asyncLoadImage.then(() => {
      image.src = 'xxx'//让真正的图片对象显示
    });



    2. Promise对象形式转换为回调函数形式
     

    //callbackify.js
    //Promise => callback
    /**
     * 
     * @param fn_promise
     * @returns {Function}
     */
    function callbackify(fn_promise) {
      if(typeof fn_promise !== 'function') throw new Error('The argument must be of type Function.');
      return function (...args) {
        let callback = args.pop();//返回一个函数 最后一个参数是回调
        if(typeof callback !== 'function') throw new Error('The last argument must be of type Function.');
        if(fn_promise() instanceof Promise){
          fn_promise(args).then(data => {
            callback(null,data)//回调执行
          }).catch(err => {
            callback(err,null)//回调执行
          })
        }else{
          throw new Error('function must be return a Promise object');
        }
      }
    }



    简单使用
     

    let func =  callbackify(timer => new Promise((resolve, reject) => {
      setTimeout(() => {resolve('hello world')},timer);
    }));
    func(1000,function (err,data) {
      console.log(data)//1s后打印hello world
    });
    


    接下来对之前的ajax例子进行改写,将回调形式变为Promise形式,可以直接改写,或使用promisify函数

     

    先看改写的

    
    //ajax_promise.js
    function ajax(object) {
      return new Promise(function (resolve,reject) {//返回个Promise对象
      //省略部分代码
        try {
         //省略部分代码 
          xhr.onreadystatechange = function () {//监听事件
            if (this.readyState === 4) {
              if (this.status === 200) {
                switch (dataType) {
                  case 'json': {
                    resolve(JSON.parse(this.responseText));//ajax有结果,resolve一下结果
                    break
                  }
                  case 'text': {
                    resolve(this.responseText);
                    break
                  }
                  case 'xml': {
                    resolve(this.responseXML);
                    break
                  }
                  default: {
                    break;
                  }
                }
              }else{
                reject(new Error('error'))
              }
            }
          }
        } catch (e) {
          reject(e)
        }
      });
    }
    
    ajax.get = function (url, data) { //get方法
        return this({url: url, method: 'GET', data: data});
    };
    ajax.post = function (url, data) { //post方法
        return this({url: url, method: 'POST', data: data});
    };
    ajax.baseUrl = '';



    简单使用

     

    //test.html
    ajax.baseUrl = 'http://localhost:3000';
    ajax.get('/api/test1',{name: 'dpf', age: 19}).then(data => {
          console.log(data)
        });
    ajax.post('/api/test2',{name: 'youname', age: 19}).then(data => {
          console.log(data)
        });
    

    不修改原代码是最好的,故看第二种方式
     

    //test.html
    ajax = promisify(ajax);
    ajax.baseUrl = 'http://localhost:3000';
    ajax.get = (url,data) => ajax({url: url, method: 'GET', data: data});
    ajax.post = (url,data) => ajax({url: url, method: 'POST', data: data});
    ajax.get('/api/test1', {name: 'dpf', age: 19}).then(([data]) => {
        console.log(data)
      });
    ajax.post('/api/test2', {name: 'youname', age: 19}).then(([data]) => {
        console.log(data)
      });
    


    Promise对象目前是比较流行的异步解决方案,相比回调函数而言,代码不再横向扩展,而且没有回调地狱这一说,好处还是挺多的,不过也有不足,就是写起来费劲(相比回调而言),不过Promise对象仍然是javascript的一个重要的知识点,而且在后面的生成器函数实现异步,async/await实现异步中会有广泛应用,希望通过刚刚的讲解,读者能对Promise对象有个基本的认识。


    4.Generator(生成器)函数

     

    Generator(生成器)函数,在python中就是创建迭代对象的,在Javascript中也是如此,不过无论在python还是JavaScript中Generator函数都还有另一个功能,即实现异步。
    Generator函数是ES6提供的一种异步编程解决方案,其行为类似于状态机。
    首先看一个简单的Generator例子
     

    function *gen(){//声明一个生成器
       let t1 = yield "hello"; //yield 表示 产出的意思 用yield来生成东西
       console.log(t1);
       let t2 = yield "world";
       console.log(t2);
    }
    let g = gen();
    /*next()返回一个{value,done}对象,value为yield表达式后面的值,done取值为true/false,表示是否  *生成结束*/  
    let x = g.next();//{value:"hello",done:false}   启动生成器
    
    
    /**
     * 通过给next()函数里传值 这里的值会传递到第一个yield表达式里 即相当于gen函数里 let t1 = "aaaa" */
    let y = g.next("aaaa");//{value:"world",done:false}
    g.next("bbbb");//{value:undefined,done:true}
    console.log(x.value,y.value);
    
    /*
    输出
    
    aaaa
    bbbb
    hello world
    */


    上面的例子中,如果把gen函数当成一个状态机,则通过调用next()方法来跳到下一个状态,即下一个yield表达式,给next()函数传值来把值传入上一个状态中,即上一个yield表达式的结果。


    在介绍Generator函数的异步时,先简单介绍一下Generator函数的几个方法

    1.next()方法:

    生成器函数里面的yield表达式并没有值,或者说总返回undefined,next()函数可以接受一个参数,该参数就会被当作yield表达式的值。

    2.throw()方法:

    在函数体外抛出一个错误,然后在函数体内捕获。例如
     

    function *gen1(){
        try{
            yield;
        }catch(e){
            console.log('内部捕获')
        }
    }
    let g1 = gen1();
    g1.next();
    g1.throw(new Error());
    
    /*
    输出
    内部捕获
    */


     

    3.return()方法:

    返回给定值,并终结生成器。例如
     

    function *gen2(){
        yield 1;
        yield 2;
        yield 3;
    }
    let g2 = gen1();
    g2.next();//{value:1,done:false}
    g2.return();//{value:undefined,done:true}
    g2.next();//{value:undefined.done:true}
    


    4.yield*表达式(类似于python的yield from):

    在生成器函数中调用另一个生成器函数。例如
     

    function *gen3(){
        yield 1;
        yield 2;
        yield 3;
    }
    function *gen4(){
        yield 4;
        yield * gen3();
        yield 5;
    }
    //等价于
    function *gen4(){
        yield 4;
        yield 1;
        yield 2;
        yield 3;
        yield 5;
    }
    


    在使用Generator(生成器)函数做异步时,先引入协程(来自python的概念)这个概念,可以理解为 "协作的函数",一个协程本质就是子函数,不过这个子函数可以执行到一半,可以暂停执行,将执行权交给其他子函数,等稍后回收执行权的时候,还可以继续执行,跟线程非常像,在c++/python/java中一个线程的单位也是一个子函数(java的run方法),线程之间的切换,就相当于函数的切换,不过这个切换代价非常大,得保存很多跟线程相关东西,而协程则没那么复杂,所以协程又被称为纤程,或轻量级线程。

    协程的执行流程大致如下:

    1.协程A开始执行。

    2.协程A执行到一半,进入暂停,执行权转移给协程B。

    3.(一段时间后)协程B交还执行权。

    4.协程A恢复执行

    其中协程A就是异步任务,因为其分多段执行。

    接下来将介绍使用Generator函数来实现协程,并做到异步。
    首先来看一个简单的例子
     

    const fs = require('fs');
    function* gen(){//生成器函数
        let data = yield asyncReadFile(__dirname+'/ajax_promise.js'); 
        console.log(data); //文件读取成功 则输出
        let data2 = yield timer(1000);
        console.log(data2); //过1s后输出 hello world
    }
    let it = gen();
    it.next();
    function timer(time){//异步任务 
        setTimeout(() => it.next('hello world'),time)
    }
    function asyncReadFile(url) {//异步任务 读取文件
        fs.readFile(url,(err,data) => {
            it.next(data.toString())
        })
    }
    


    可以看出通过暂缓it.next()方法的执行,来实现异步的功能,如果仅看gen的函数里面内部,比如
    let data = yield asyncReadFile(__dirname+'/ajax_promise.js'); 这一段,可以理解为data等待异步读取文件asyncReadFile的结果,如果有了结果,则输出,gen继续向下执行,不过每一个异步函数,比如asyncReadFile的实现却变麻烦了,这个时候就要借助Promise对象,例子如下
     

    const promisify = require('./promisify');
    function timer(time,callback){
        setTimeout(() => callback(), time)
    }
    const asyncReadFile = promisify(require('fs').readFile);//借用之前的promisify方法,将callback形式转换为Promise
    const asyncTimer = promisify(timer);
    function *gen(){
        let [data] = yield asyncReadFile('./a.mjs');//生成一个Promise对象
        console.log(data);
        yield asyncTimer(1000);
        console.log('hello world');
    }
    let g = gen();
    let {value} = g.next(); //{value:asyncReadFile('./a.mjs'),done:false}
    value.then(data => {//相当于asyncReadFile('./a.mjs').then(data => {})
        let {value} = g.next(data);//{value:asyncTimer(1000),done:false}
        value.then(data => {//相当于asyncTimer(1000).then(data => {})
            g.next(data);//{value:undefined,done:true}
        })
    });
    


    可以看出上面的借助Promise对象例子,在异步处理上可以有更通用的实现,即生成器执行器,
     

    //run.js
    function run(gen){//传入一个生成器函数
        let g = gen();
        function next(data){
            let result = g.next(data);
            let {value,done} = result;
            if(done) return value;//done为true时结束递归
            if (Array.isArray(value)) value =  Promise.all(value);//如果yield表达式后面跟的是一个数组,可以将其转换为Promise.all
            if(!value instanceof Promise) value = Promise.resolve(value)//不是Promise对象,则转成Promise对象
            value.then((data) => {
                next(data);//递归调用
            });
        }
        next();//启动生成器
    }



    借助run执行器函数,运行上面的gen只需要run(gen)即可
    最后让我们来继续改写之前的ajax例子,这次使用Generator函数,代码如下
     

    //test.html
      ajax = promisify(ajax);
      ajax.baseUrl = 'http://localhost:3000';
      ajax.get = (url,data) => ajax({url: url, method: 'GET', data: data});
      ajax.post = (url,data) => ajax({url: url, method: 'POST', data: data});
      run(function*(){
        let [[data1],[data2]] = yield [ajax.get('/api/test1', {name: 'dpf', age: 19}),ajax.post('/api/test2', {name: 'youname', age: 19})];//相当于Promise.all
        console.log(data1,data2)
      });
    


    使用Generator函数无疑是解决异步的优于callback(回调),及Promise对象的好方法,没有callback回调地狱,Promise对象的过长then链,异步代码看起来跟同步代码一样,可读性,和维护性都较好。

     

    5.async/await(javascript异步的终极解决方案)

    es6中使用Generator函数来做异步,在ES2017中,提供了async/await两个关键字来实现异步,让异步变得更加方便。
    async/await本质上还是基于Generator函数,可以说是Generator函数的语法糖,async就相当于之前写的run函数(执行Generator函数的函数),而await就相当于yield,只不过await表达式后面只能跟着Promise对象,如果不是Promise对象的话,会通过Promise.resolve方法使之变成Promise对象。async修饰function,其返回一个Promise对象。await必须放在async修饰的函数里面,就相当于yield只能放在Generator生成器函数里一样。一个简单的例子
     

    //封装一个定时器,返回一个Promise对象
    const timer = time => new Promise((resolve,reject) => {
        setTimeout(() => resolve('hello world'),time)
    });
    
    async function main() {//async函数
        let start = Date.now();
        let data = await timer(1000);//可以把await理解为 async wait 即异步等待(虽然是yield的变体),当Promise对象有值的时候将值返回,即Promise对象里resolve(data)里面的data,作为await表达式的结果
        console.log(data,'time = ',Date.now() - start,'ms')//将会输出 hello world time =  1002 ms
    }
    main();



    可以看到async/await使用起来非常方便,其实async/await的原理也非常简单,就是把Generator函数和执行器包装在一起,其实现如下
     

    //spawn.js 
    //之前的run函数的变体,只不过多了错误处理,然后返回的是Promise对象
    function spawn(genF){
        return new Promise((resolve,reject) => {
            let g = genf();
            function next(nextF){
                let next;
                try{
                    next = nextF();
                }catch(e){
                    reject(e)
                }
                if(next.done) return resolve(next.value);
                Promise.resolve(next.value)
                       .then(data => next(() => g.next(data)))
                       .catch(err => next(() => g.throw(err)));
            }
            next(() => g.next(undefined))
        })
    }



    所以之前的async function main() {} 就等价于 function main() { return spawn(function *() {}) },了解async的内部原理可以有助于理解和使用async。

    接下来看使用async/await来改进之前的ajax的例子
     

    //test.html
    ajax = promisify(ajax);
    ajax.baseUrl = 'http://localhost:3000';
    ajax.get = (url,data) => ajax({url: url, method: 'GET', data: data});
    ajax.post = (url,data) => ajax({url: url, method: 'POST', data: data});
     
    (async function() {
        let [data1,data2] = await Promise.all([ajax.get('/api/test1', {name: 'dpf', age: 19}),ajax.post('/api/test2', {name: 'youname', age: 19})]);
        console.log(data1,data2)
    })() 
    


    到此,这篇文章已经接近尾声,总结一下JavaScript实现异步的这五种方式的优缺点


    1.callback(回调函数):写起来方便,不过过多的回调会产生回调地狱,代码横向扩展,过多的回调不易于维护和理解

    2.发布订阅模式:通过实现个事件管理器,方便管理和修改事件,不同的事件对应不同的回调,通触发事件来实现异步,不过会产生一些命名冲突的问题(在原来的Event.js基础上加个命名空间,防止命名冲突即可),事件到处触发,可能代码可读性不好。

    3.Promise对象:本质是用来解决回调产生的代码横向扩展,及可读性不强的问题,通过.then方法来替代掉回调,而且then方法接的参数也有限制,所以解决了,回调产生的参数不容易确定的问题,缺点的话,个人觉得,写起来可能不那么容易,不过写好了,用起来就就方便多了。

    4.Generator(生成器)函数:记得第一次接触Generator函数是在python中,而且协程的概念,以及使用生成器函数来实现异步,也是在python中学到的,感觉javascript有点是借鉴到python语言中的,不过确实很好的解决了JavaScript中异步的问题,不过得依赖执行器函数。

    5.async/await:这种方式可能是javascript中,解决异步的最好的方式了,让异步代码写起来跟同步代码一样,可读性和维护性都上来了。

    最后文章中的所有代码,均在我的github上
    [https://github.com/sundial-dreams/javascript_async]()

    ,希望这篇文章能让你对JavaScript异步有一定的认识。


     

    展开全文
  • 主要介绍了新手如何快速理解js异步编程,异步编程从早期的 callback、事件发布订阅模式到 ES6 的 Promise、Generator 在到 ES2017 中 async,看似风格迥异,但是还是有一条暗线将它们串联在一起的,,需要的朋友可以...
  • 下面小编就为大家带来一篇浅析javascript异步执行函数导致的变量变化问题解决思路。小编觉得挺不错的,现在分享给大家,也给大家做个参考
  • 主要介绍了详解JS异步加载的三种方式,非常不错,具有参考借鉴价值,需要的朋友可以参考下
  • JavaScript异步回调 then.js ,then.js Another very small promise! 能用简单优美的方式将任何同步...
  • 主要介绍了点评js异步加载的4种方式,帮助大家更全面的了解js异步加载方式,感兴趣的小伙伴们可以参考一下
  • JavaScript异步加载图片

    2019-11-07 17:06:22
    异步的内容很简单,封装一个新建Image的函数,让所有图片都加载好之后返回即可。这样就可以完全等待图片加载成功后执行下一步。 newImageProcess : function ( src ) { return new Promise ( ( resolve...
  • script.js异步 JavaScript 加载器和依赖管理器
  • JS 异步编程六种方案

    万次阅读 多人点赞 2019-06-28 11:15:45
    我们知道Javascript语言的执行环境是"单线程"。也就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务。 这种模式虽然实现起来比较简单,执行环境相对单纯,但是只要有...
  • 主要介绍了Node.js异步处理的各种写法,本文给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
  • js异步函数(async/await)

    2021-07-20 21:48:46
    异步函数也称为“asynac/await”(语法关键字),是ES6期约模式在ECMAScript函数中的应用。async/await是ES8新增的。这个特性让以同步方式写的代码能够异步执行。 2.async async关键字用于声明异步函数。这个关键字...
  • JavaScript异步编程

    2018-03-24 03:45:31
    JavaScript 最初设计时是为了强化 Netscape 2.0 浏览器的网页表现力, 现在却成为了多媒体、多任务、多内核网络世界中的一种单线程语言。...于是,Node.js 就此诞生,JavaScript 成为了服务器世界可以倚重的力量之一。
  • 详解JavaScript异步编程技术

    千次阅读 2017-02-08 17:30:12
    详解JavaScript异步编程技术基于浏览器的事件轮询机制(以及Node.js中的事件轮询机制),JavaScript常常会运行在异步环境中。由于JavaScript本身语言的特性(不需要程序员操控线程/进程),在js中解决异步化编程的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 383,336
精华内容 153,334
关键字:

js异步