精华内容
下载资源
问答
  • JS实现异步编程几种方式

    千次阅读 2016-08-09 09:57:40
    转载出处:... Javascript异步编程的4方法 作者: 阮一峰 日期: 2012年12月21日 你可能知道,Javascript语言的执行环境是"单线程"(single thre

    转载出处:http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html

    Javascript异步编程的4种方法

    作者: 阮一峰

    日期: 2012年12月21日

    你可能知道,Javascript语言的执行环境是"单线程"(single thread)。

    所谓"单线程",就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。

    这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。

    为了解决这个问题,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。

    "同步模式"就是上一段的模式,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的;"异步模式"则完全不同,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。

    "异步模式"非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。在服务器端,"异步模式"甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。

    本文总结了"异步模式"编程的4种方法,理解它们可以让你写出结构更合理、性能更出色、维护更方便的Javascript程序。

    一、回调函数

    这是异步编程最基本的方法。

    假定有两个函数f1和f2,后者等待前者的执行结果。

      f1();

      f2();

    如果f1是一个很耗时的任务,可以考虑改写f1,把f2写成f1的回调函数。

      function f1(callback){

        setTimeout(function () {

          // f1的任务代码

          callback();

        }, 1000);

      }

    执行代码就变成下面这样:

      f1(f2);

    采用这种方式,我们把同步操作变成了异步操作,f1不会堵塞程序运行,相当于先执行程序的主要逻辑,将耗时的操作推迟执行。

    回调函数的优点是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱,而且每个任务只能指定一个回调函数。

    二、事件监听

    另一种思路是采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。

    还是以f1和f2为例。首先,为f1绑定一个事件(这里采用的jQuery的写法)。

      f1.on('done', f2);

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

      function f1(){

        setTimeout(function () {

          // f1的任务代码

          f1.trigger('done');

        }, 1000);

      }

    f1.trigger('done')表示,执行完成后,立即触发done事件,从而开始执行f2。

    这种方法的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以"去耦合"(Decoupling),有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。

    三、发布/订阅

    上一节的"事件",完全可以理解成"信号"。

    我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern),又称"观察者模式"(observer pattern)。

    这个模式有多种实现,下面采用的是Ben Alman的Tiny Pub/Sub,这是jQuery的一个插件。

    首先,f2向"信号中心"jQuery订阅"done"信号。

      jQuery.subscribe("done", f2);

    然后,f1进行如下改写:

      function f1(){

        setTimeout(function () {

          // f1的任务代码

          jQuery.publish("done");

        }, 1000);

      }

    jQuery.publish("done")的意思是,f1执行完成后,向"信号中心"jQuery发布"done"信号,从而引发f2的执行。

    此外,f2完成执行后,也可以取消订阅(unsubscribe)。

      jQuery.unsubscribe("done", f2);

    这种方法的性质与"事件监听"类似,但是明显优于后者。因为我们可以通过查看"消息中心",了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。

    四、Promises对象

    Promises对象是CommonJS工作组提出的一种规范,目的是为异步编程提供统一接口

    简单说,它的思想是,每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指定回调函数。比如,f1的回调函数f2,可以写成:

      f1().then(f2);

    f1要进行如下改写(这里使用的是jQuery的实现):

      function f1(){

        var dfd = $.Deferred();

        setTimeout(function () {

          // f1的任务代码

          dfd.resolve();

        }, 500);

        return dfd.promise;

      }

    这样写的优点在于,回调函数变成了链式写法,程序的流程可以看得很清楚,而且有一整套的配套方法,可以实现许多强大的功能。

    比如,指定多个回调函数:

      f1().then(f2).then(f3);

    再比如,指定发生错误时的回调函数:

      f1().then(f2).fail(f3);

    而且,它还有一个前面三种方法都没有的好处:如果一个任务已经完成,再添加回调函数,该回调函数会立即执行。所以,你不用担心是否错过了某个事件或信号。这种方法的缺点就是编写和理解,都相对比较难。


    展开全文
  • JS 异步编程方案

    万次阅读 多人点赞 2019-06-28 11:15:45
    本文主要介绍异步编程几种办法,并通过比较,得到最佳异步编程的解决方案! 想阅读更多优质文章请猛戳 GitHub博客 ,一年五十篇优质文章等着你! 一、同步与异步 我们可以通俗理解为异步就是一个任务分成两段,...

    前言

    我们知道Javascript语言的执行环境是"单线程"。也就是指一次只能完成一件任务。如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务。

    这种模式虽然实现起来比较简单,执行环境相对单纯,但是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。

    为了解决这个问题,Javascript语言将任务的执行模式分成两种:同步和异步。本文主要介绍异步编程几种办法,并通过比较,得到最佳异步编程的解决方案!

    想阅读更多优质文章请猛戳GitHub博客,一年五十篇优质文章等着你!

    一、同步与异步

    我们可以通俗理解为异步就是一个任务分成两段,先执行第一段,然后转而执行其他任务,等做好了准备,再回过头执行第二段。排在异步任务后面的代码,不用等待异步任务结束会马上运行,也就是说,异步任务不具有”堵塞“效应。比如,有一个任务是读取文件进行处理,异步的执行过程就是下面这样

    在这里插入图片描述

    这种不连续的执行,就叫做异步。相应地,连续的执行,就叫做同步

    在这里插入图片描述

    "异步模式"非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。在服务器端,"异步模式"甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。接下来介绍下异步编程六种方法。

    二、回调函数(Callback)

    回调函数是异步操作最基本的方法。以下代码就是一个回调函数的例子:

    ajax(url, () => {
        // 处理逻辑
    })
    

    但是回调函数有一个致命的弱点,就是容易写出回调地狱(Callback hell)。假设多个请求存在依赖性,你可能就会写出如下代码:

    ajax(url, () => {
        // 处理逻辑
        ajax(url1, () => {
            // 处理逻辑
            ajax(url2, () => {
                // 处理逻辑
            })
        })
    })
    

    回调函数的优点是简单、容易理解和实现,缺点是不利于代码的阅读和维护,各个部分之间高度耦合,使得程序结构混乱、流程难以追踪(尤其是多个回调函数嵌套的情况),而且每个任务只能指定一个回调函数。此外它不能使用 try catch 捕获错误,不能直接 return。

    三、事件监听

    这种方式下,异步任务的执行不取决于代码的顺序,而取决于某个事件是否发生

    下面是两个函数f1和f2,编程的意图是f2必须等到f1执行完成,才能执行。首先,为f1绑定一个事件(这里采用的jQuery的写法)

    f1.on('done', f2);
    

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

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

    上面代码中,f1.trigger(‘done’)表示,执行完成后,立即触发done事件,从而开始执行f2。

    这种方法的优点是比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以"去耦合",有利于实现模块化。缺点是整个程序都要变成事件驱动型,运行流程会变得很不清晰。阅读代码的时候,很难看出主流程。

    四、发布订阅

    我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做"发布/订阅模式"(publish-subscribe pattern),又称"观察者模式"(observer pattern)。

    首先,f2向信号中心jQuery订阅done信号。

    jQuery.subscribe('done', f2);
    

    然后,f1进行如下改写:

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

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

    jQuery.unsubscribe('done', f2);
    

    这种方法的性质与“事件监听”类似,但是明显优于后者。因为可以通过查看“消息中心”,了解存在多少信号、每个信号有多少订阅者,从而监控程序的运行。

    五、Promise/A+

    Promise本意是承诺,在程序中的意思就是承诺我过一段时间后会给你一个结果。 什么时候会用到过一段时间?答案是异步操作,异步是指可能比较长时间才有结果的才做,例如网络请求、读取本地文件等

    1.Promise的三种状态

    • Pending----Promise对象实例创建时候的初始状态
    • Fulfilled----可以理解为成功的状态
    • Rejected----可以理解为失败的状态

    在这里插入图片描述

    这个承诺一旦从等待状态变成为其他状态就永远不能更改状态了,比如说一旦状态变为 resolved 后,就不能再次改变为Fulfilled

    let p = new Promise((resolve, reject) => {
      reject('reject')
      resolve('success')//无效代码不会执行
    })
    p.then(
      value => {
        console.log(value)
      },
      reason => {
        console.log(reason)//reject
      }
    )
    

    当我们在构造 Promise 的时候,构造函数内部的代码是立即执行的

    new Promise((resolve, reject) => {
      console.log('new Promise')
      resolve('success')
    })
    console.log('end')
    // new Promise => end
    

    2.promise的链式调用

    • 每次调用返回的都是一个新的Promise实例(这就是then可用链式调用的原因)
    • 如果then中返回的是一个结果的话会把这个结果传递下一次then中的成功回调
    • 如果then中出现异常,会走下一个then的失败回调
    • 在 then中使用了return,那么 return 的值会被Promise.resolve() 包装(见例1,2)
    • then中可以不传递参数,如果不传递会透到下一个then中(见例3)
    • catch 会捕获到没有捕获的异常

    接下来我们看几个例子:

      // 例1
      Promise.resolve(1)
      .then(res => {
        console.log(res)
        return 2 //包装成 Promise.resolve(2)
      })
      .catch(err => 3)
      .then(res => console.log(res))
    
    // 例2
    Promise.resolve(1)
      .then(x => x + 1)
      .then(x => {
        throw new Error('My Error')
      })
      .catch(() => 1)
      .then(x => x + 1)
      .then(x => console.log(x)) //2
      .catch(console.error)
    
    // 例3
    let fs = require('fs')
    function read(url) {
      return new Promise((resolve, reject) => {
        fs.readFile(url, 'utf8', (err, data) => {
          if (err) reject(err)
          resolve(data)
        })
      })
    }
    read('./name.txt')
      .then(function(data) {
        throw new Error() //then中出现异常,会走下一个then的失败回调
      }) //由于下一个then没有失败回调,就会继续往下找,如果都没有,就会被catch捕获到
      .then(function(data) {
        console.log('data')
      })
      .then()
      .then(null, function(err) {
        console.log('then', err)// then error
      })
      .catch(function(err) {
        console.log('error')
      })
    

    Promise不仅能够捕获错误,而且也很好地解决了回调地狱的问题,可以把之前的回调地狱例子改写为如下代码:

    ajax(url)
      .then(res => {
          console.log(res)
          return ajax(url1)
      }).then(res => {
          console.log(res)
          return ajax(url2)
      }).then(res => console.log(res))
    

    它也是存在一些缺点的,比如无法取消 Promise,错误需要通过回调函数捕获。

    六、生成器Generators/ yield

    Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同,Generator 最大的特点就是可以控制函数的执行。

    • 语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
    • Generator 函数除了状态机,还是一个遍历器对象生成函数
    • 可暂停函数, yield可暂停,next方法可启动,每次返回的是yield后的表达式结果
    • yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值

    我们先来看个例子:

    function *foo(x) {
      let y = 2 * (yield (x + 1))
      let z = yield (y / 3)
      return (x + y + z)
    }
    let it = foo(5)
    console.log(it.next())   // => {value: 6, done: false}
    console.log(it.next(12)) // => {value: 8, done: false}
    console.log(it.next(13)) // => {value: 42, done: true}
    

    可能结果跟你想象不一致,接下来我们逐行代码分析:

    • 首先 Generator 函数调用和普通函数不同,它会返回一个迭代器
    • 当执行第一次 next 时,传参会被忽略,并且函数暂停在 yield (x + 1) 处,所以返回 5 + 1 = 6
    • 当执行第二次 next 时,传入的参数12就会被当作上一个yield表达式的返回值,如果你不传参,yield 永远返回 undefined。此时 let y = 2 * 12,所以第二个 yield 等于 2 * 12 / 3 = 8
    • 当执行第三次 next 时,传入的参数13就会被当作上一个yield表达式的返回值,所以 z = 13, x = 5, y = 24,相加等于 42

    我们再来看个例子:有三个本地文件,分别1.txt,2.txt和3.txt,内容都只有一句话,下一个请求依赖上一个请求的结果,想通过Generator函数依次调用三个文件

    //1.txt文件
    2.txt
    
    //2.txt文件
    3.txt
    
    //3.txt文件
    结束
    
    let fs = require('fs')
    function read(file) {
      return new Promise(function(resolve, reject) {
        fs.readFile(file, 'utf8', function(err, data) {
          if (err) reject(err)
          resolve(data)
        })
      })
    }
    function* r() {
      let r1 = yield read('./1.txt')
      let r2 = yield read(r1)
      let r3 = yield read(r2)
      console.log(r1)
      console.log(r2)
      console.log(r3)
    }
    let it = r()
    let { value, done } = it.next()
    value.then(function(data) { // value是个promise
      console.log(data) //data=>2.txt
      let { value, done } = it.next(data)
      value.then(function(data) {
        console.log(data) //data=>3.txt
        let { value, done } = it.next(data)
        value.then(function(data) {
          console.log(data) //data=>结束
        })
      })
    })
    // 2.txt=>3.txt=>结束
    

    从上例中我们看出手动迭代Generator 函数很麻烦,实现逻辑有点绕,而实际开发一般会配合 co 库去使用。co是一个为Node.js和浏览器打造的基于生成器的流程控制工具,借助于Promise,你可以使用更加优雅的方式编写非阻塞代码

    安装co库只需:npm install co

    上面例子只需两句话就可以轻松实现

    function* r() {
      let r1 = yield read('./1.txt')
      let r2 = yield read(r1)
      let r3 = yield read(r2)
      console.log(r1)
      console.log(r2)
      console.log(r3)
    }
    let co = require('co')
    co(r()).then(function(data) {
      console.log(data)
    })
    // 2.txt=>3.txt=>结束=>undefined
    

    我们可以通过 Generator 函数解决回调地狱的问题,可以把之前的回调地狱例子改写为如下代码:

    function *fetch() {
        yield ajax(url, () => {})
        yield ajax(url1, () => {})
        yield ajax(url2, () => {})
    }
    let it = fetch()
    let result1 = it.next()
    let result2 = it.next()
    let result3 = it.next()
    

    七、async/await

    1.Async/Await简介

    使用async/await,你可以轻松地达成之前使用生成器和co函数所做到的工作,它有如下特点:

    • async/await是基于Promise实现的,它不能用于普通的回调函数。
    • async/await与Promise一样,是非阻塞的。
    • async/await使得异步代码看起来像同步代码,这正是它的魔力所在。

    一个函数如果加上 async ,那么该函数就会返回一个 Promise

    async function async1() {
      return "1"
    }
    console.log(async1()) // -> Promise {<resolved>: "1"}
    

    Generator函数依次调用三个文件那个例子用async/await写法,只需几句话便可实现

    let fs = require('fs')
    function read(file) {
      return new Promise(function(resolve, reject) {
        fs.readFile(file, 'utf8', function(err, data) {
          if (err) reject(err)
          resolve(data)
        })
      })
    }
    async function readResult(params) {
      try {
        let p1 = await read(params, 'utf8')//await后面跟的是一个Promise实例
        let p2 = await read(p1, 'utf8')
        let p3 = await read(p2, 'utf8')
        console.log('p1', p1)
        console.log('p2', p2)
        console.log('p3', p3)
        return p3
      } catch (error) {
        console.log(error)
      }
    }
    readResult('1.txt').then( // async函数返回的也是个promise
      data => {
        console.log(data)
      },
      err => console.log(err)
    )
    // p1 2.txt
    // p2 3.txt
    // p3 结束
    // 结束
    

    2.Async/Await并发请求

    如果请求两个文件,毫无关系,可以通过并发请求

    let fs = require('fs')
    function read(file) {
      return new Promise(function(resolve, reject) {
        fs.readFile(file, 'utf8', function(err, data) {
          if (err) reject(err)
          resolve(data)
        })
      })
    }
    function readAll() {
      read1()
      read2()//这个函数同步执行
    }
    async function read1() {
      let r = await read('1.txt','utf8')
      console.log(r)
    }
    async function read2() {
      let r = await read('2.txt','utf8')
      console.log(r)
    }
    readAll() // 2.txt 3.txt
    

    八、总结

    1.JS 异步编程进化史:callback -> promise -> generator -> async + await

    2.async/await 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。

    3.async/await可以说是异步终极解决方案了。

    (1) async/await函数相对于Promise,优势体现在:

    • 处理 then 的调用链,能够更清晰准确的写出代码
    • 并且也能优雅地解决回调地狱问题。

    当然async/await函数也存在一些缺点,因为 await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低,代码没有依赖性的话,完全可以使用 Promise.all 的方式。
    (2) async/await函数对 Generator 函数的改进,体现在以下三点:

    • 内置执行器。

    Generator 函数的执行必须靠执行器,所以才有了 co 函数库,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行

    • 更广的适用性。

    co 函数库约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)

    • 更好的语义。

    async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。
    在这里插入图片描述

    参考文章

    展开全文
  • 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异步有一定的认识。


     

    展开全文
  • 一、通过委托实现异步 namespace Test1 { class AsyncDemo { public void async() { string i = "参数"; Console.WriteLine("调用异步方法前"); PostAsync(i); Console.WriteLine("调用异步方法后"); } ...

    一、通过委托实现异步

    namespace Test1
    {
        class AsyncDemo
        {
            public void async()
            {
                string i = "参数";
                Console.WriteLine("调用异步方法前");
                PostAsync(i);
                Console.WriteLine("调用异步方法后");
            }
            delegate void AsyncFoo(string i);
            private static void PostAsync(object o)
            {
                AsyncFoo caller = Myfunc;
                caller.BeginInvoke(o.ToString(), FooCallBack, caller);
            }
            private static void FooCallBack(IAsyncResult ar)
            {
                var caller = (AsyncFoo)ar.AsyncState;
                caller.EndInvoke(ar);
            }
            private static void Myfunc(string i)
            {
                Console.WriteLine("通过委托来实现异步编程的");
            }
        }
    }
    

    二、通过Task实现

    namespace Test1
    {
        class TaskDemo
        {
            public void taskDemo()
            {
                Console.WriteLine("主线程,线程ID:"+Thread.CurrentThread.ManagedThreadId);
                //Task方式1
                Task task1= new Task(()=>TaskFunc1());
                task1.Start();
                //Task方式2
                var result = Task.Run<string>(()=>{ return TaskFunc2(); });
                Console.WriteLine(result.Result);
            }
            private static void TaskFunc1()
            {
                Console.WriteLine("Task方式1开启的线程ID:"+Thread.CurrentThread.ManagedThreadId);
                for(int i = 0; i < 100; i++)
                {
                    Console.WriteLine(i);
                }
            }
            private static string  TaskFunc2()
            {
                for (int i = 100; i < 200; i++)
                {
                    Console.WriteLine(i);
                }
                return "Task方式2开启的线程ID:"+ Thread.CurrentThread.ManagedThreadId;
            }
        }
    }
    

    三、通过await async实现

    namespace Test1
    {
        class awaitAsyncDemo
        {
            public void Demo()
            {
                Console.WriteLine("主线程,线程ID:" + Thread.CurrentThread.ManagedThreadId);
               
                var result =  AsyncFunc();
                Console.WriteLine(result.Result);
            }
            private static async Task<string> AsyncFunc()
            {
                return await Task.Run(() =>
                {
                    Console.WriteLine("await/async的线程ID:"+Thread.CurrentThread.ManagedThreadId);
                    return "这是返回值";
                });
            }
            
        }
    }
    
    展开全文
  • 异步编程有哪几种方法来实现

    千次阅读 2018-06-22 10:52:19
    异步编程有哪几种方法来实现?1.背景介绍 JavaScript的执行环境是单线程的,单线程的好处是执行环境简单,不用去考虑诸如资源同步,死锁等多线程阻塞式编程等所需要面对的恼人的问题。但带来的坏处是当一个任务执行...
  • 前言因项目需要从LiveScript转为ES6, 所以最近看了阮一峰的ES6教程,主要感兴趣的是ES6对JS的异步编程新的解决方案,ES6增加了promise和Generator等解决方法。现在我们来大致理清一下到ES6为止的JS异步解决的思路...
  • C#异步编程方式以及示例

    千次阅读 2018-07-12 10:53:30
    C#异步编程,这里列举一下几种方式:1、通过委托来实现异步(在委托类型中定义了BeginInvoke()和EndInvoke()两个方法);2、await async (.net4.5新特性)3、task(.net 4.0 新特性)下面看示例:一、通过委托来实现...
  • 异步编程有哪几种方法来实现?

    千次阅读 2018-07-11 17:15:13
    问:异步编程几种方法 答: 1 回调函数 2 事件监听 3 Promise 4 Generation 5 async/await 问:promise是什么 答: Promises对象是CommonJS工作组提出的一种规范,目的是为异步编程提供统一接口。Promise是后面新...
  • JavaScript 异步编程

    千次阅读 多人点赞 2021-03-14 20:32:06
    JavaScript 异步编程单线程的优势和弊端同步模式与异步模式同步模式 Synchronous异步模式 Asynchronous事件循环与消息队列异步编程几种方式Promise 异步方案、宏任务/微任务队列Generator 异步方案、Async/Await ...
  • 前文中曾介绍过可以通过create_task创建task并异步执行,这是对单个任务而言的,本文主要讨论任务组(task group)的管理,WinRT中提出了多种进行task group管理的方法,接下来开始一一说明。 1. Concurrency::...
  • JS异步编程的四方法

    千次阅读 2018-08-06 11:19:59
    一、回调函数,这是异步编程最基本的方法 假定有两个函数f1和f2,后者等待前者的执行结果,如果f1是一个很耗时的任务,可以考虑改写f1,把f2写成f1的回调函数。 function f1(callback){  setTimeout(function ...
  • Dart 异步编程详解

    千次阅读 2019-05-05 13:18:50
    文章目录Dart 异步编程Dart 的事件循环调度任务延时任务Future 详解创建 Future注册回调async 和 awaitIsolatespawnUrispawn使用场景 Dart 异步编程 编程中的代码执行,通常分为同步与异步两。简单说,同步就是...
  • 本文不涉及如何优雅的设计和实现异步编程,只是从最基础的方面阐述异步、同步编程的方式。 如想优化实现,请移步: javascript 异步编程2 JavaScript异步编程原理 Javascript异步编程的4方法 (似乎不应该...
  • 一、回调函数(callback) 是啥:一个函数a被当做另一个函数b的参数,在b的函数体内去执行a ...优化: 变成异步回调 function b(callback){   setTime(()=>{ callback(); },1000) } functi
  • PHP 异步编程

    千次阅读 2020-01-03 10:33:05
    1. 应用场景 用于项目开发练习, 提高知识技能....php异步编程的详解(附示例) https://blog.csdn.net/william_n/article/details/115206304 // PHP异步执行的几种常用方式 - 学习/实践 后续补充 ...
  • 详解JavaScript异步编程技术

    千次阅读 2017-02-08 17:30:12
    详解JavaScript异步编程技术基于浏览器的事件轮询机制(以及Node.js中的事件轮询机制),JavaScript常常会运行在异步环境中。...本文将详细介绍几种经典JavaScript异步编程串行化方法,同时也将简单介绍一下
  • Java8新的异步编程方式 CompletableFuture

    千次阅读 2016-10-17 16:30:03
    Future接口是Java多线程Future模式的实现,在java.util.concurrent包中,可以来进行异步计算。Future模式是多线程设计常用的一设计模式。Future模式可以理解成:我有一个任务,提交给了Future,Future替我完成这个...
  • c++ 异步编程

    千次阅读 2019-10-10 13:16:22
    但是在网上找了个demo之后,发现调用复杂的库(future等)和模块用到的库可能存在潜在的冲突,结果future好像和tensorRT冲突,thread好像和openGL冲突。存在一些问题。试来试去,还是最简单的thread比较好使,...
  • iPhone异步编程初探

    千次阅读 2012-08-23 15:47:01
    异步编程从来是与硬件和系统紧密相关的, 不同的硬件架构和不同的系统架构有不同的异步模型, 自然也会带来不同的异步编程方式。 要深究iOS上的异步编程, 首先就要了解iOS的异步模型。 2.1 事件驱
  • javascript异步编程

    千次阅读 2015-06-27 07:23:43
    异步机制JavaScript的执行环境是单线程的,单线程的好处是执行环境简单,不用去考虑诸如资源同步,死锁等多线程阻塞式编程等所需要面对的恼人的问题。但带来的坏处是当一个任务执行时间较长时,后面的任务会等待很长...
  • 什么是JavaScript异步编程

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

    千次阅读 2009-03-27 17:34:00
    Twisted异步编程并发编程介绍等待回应不等待数据非阻塞调用callbackDeferredDeferred解决的问题Deferred——数据即将到来的信号callback错误处理:errback结论参考资料这篇文档介绍了异步编程模型,以及在Twisted中...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 155,457
精华内容 62,182
关键字:

异步编程的几种方式