Javascript 异步编程方法汇总

  1. 回调函数
    假定有两个函数f1和f2,后者等待前者的执行结果。
    f1(); f2();
    如果f1是一个很耗时的任务,可以考虑改写为f1,把f2写为f1的回调函数。
    function f1(callback){
       setTimeout(function(){
            //f1代码
            callback();
        },1000);
    }
    
    执行的时候就变成了f1(f2); 采用这种方法把同步变成异步,f1不会堵塞程序运行,相当于先执行程序的主要逻辑,将耗时都部分推迟执行。
    回调函数优点:简单、容易理解和部署;缺点:不利于代码的阅读和维护。
    一个同步(阻塞)中使用回调的例子,目的是在func1代码执行完成后执行func2代码。
    var func1 = function(callback){
           // do something 
          (callback && typeof(callback) === "function") && callback();
    }
    func1(func2);
    var func2 = function(){}
    

更多回调函数请参考:

  • JavaScript回调函数
  • 浅谈 javascript 回调函数
  • JavaScript:回调是什么鬼?
  • JS 回调函数详解
  1. 事件监听
    任务的执行不取决于代码的顺序,而取决于某件事件是否发生。
    为f1绑定一个事件:f1.on('done', f2); 当f1发生done事件,就执行f2。然后对f1进行改写为
    function f1(){
    setTimeout(function*(){
         // f1的任务代码
         f1.trigger('done');
        },1000);
    }
    
    优点:比较容易理解,绑定多个事件,每个事件可以指定多个回调函数,去耦合,实现模块化。
  2. 发布/订阅
    例如:jquery中一个插件:Tiny Pub/Sub
    1. f2向信号中心"jquery订阅"done信号: jquery.subscribe("done", f2);
    2. f1函数如下:
      function f1(){
          setTimeout(function(){
                // f1的执行代码
                 jquey.publish('done');
          }, 1000);
       }
      
      jquey.publish('done');就是f1执行完毕之后,向信号中心发送done信号,从而引发f2的执行。f2执行后,也可以取消订阅jquery.unsubscribe("done", f2);
  3. Promises对象:为异步编程提供统一接口。
    思想:每一个异步任务返回一个Promise对象,该对象有一个then方法,允许指向回调函数。
    Promise的三种状态:
    • Pending:Promise对象实例创建时候的初始状态。
    • Fulfilled:成功的状态
    • Rejected:失败的状态
      Promise ---> resolved ---> then(回调callback) 或者 ---> rejected ---> catch(回调callback)。Promise一旦从等待状态变成其他状态就永远不能改变状态了。
       const instance = new Promise((resolve, reject) => {
         // 一些异步操作
         if(/*异步操作成功*/) {
             resolve(value);
         } else {
             reject(error);
             }
         }
       })
       instance.then(value => {
           // do something...
        }, error => {
             // do something...
        })
      
      构造函数内部的代码立刻执行。
      then 方法会返回一个新的 Promise 实例,可以分两种情况来看:
      • 指定返回值是新的 Promise 对象,如return new Promise(...),这种情况没啥好说的,由于返回的是 Promise,后面显然可以继续调用then方法。
      • 返回值不是Promise, 如:return 1 这种情况还是会返回一个 Promise,并且这个Promise 立即执行回调 resolve(1)。所以仍然可以链式调用then方法。(注:如果没有指定return语句,相当于返回了undefined)。
      1. 示例一
      function sayHi(name) {
         return new Promise((resolve, reject) => {
               setTimeout(() => {
                   resolve(name);
                }, 2000)
          })
      }
      sayHi('张三')
         .then(name => {
               console.log(`你好, ${name}`);
               return sayHi('李四');    // 最终 resolved 函数中的参数将作为值传递给下一个then
            })
           // name 是上一个then传递出来的参数
         .then(name => {                
             console.log(`你好, ${name}`);
             return sayHi('王二麻子');
           })
         .then(name => {
             console.log(`你好, ${name}`);
         })
       // 你好, 张三
       // 你好, 李四
       // 你好, 王二麻子
      
      1. 示例二
       read('./file-01.txt', 'utf8')
          .then(data => {
            // 因为 read 方法会返回一个 promise ,返回read执行相当于返回一个 promise
            // 会将这个 promise 的执行成功的结果传递给下一次 then 的 resolve 
                return read(data, 'utf8');
            }, err => {
                console.log(err);
          })
          .then(data => {
            // 如果返回一个普通的值 ,会将这个普通值传递倒下一次 then 的成功的参数
              return [data];
           }, err => {
             console.log(err);
           })
          .then(data => {
            // 返回的是一个普通值
             console.log(data);
             // 没有写 return ,相当于返回一个 undefined ,下一个then的成功值则为 undefined
              }, err => {
              console.log(err);
            })
          .then(data => {
          // 上一个 then 没有返回值,默认值为 undefined
          // undefined 也算成功
            console.log(data);
            // 抛出错误,将传给下一个 then 的 reject 
              throw new Error('xxx');
          }, err => {
              console.log(err);
        })
         .then(null, err => {
            // 如果上一个 then 抛出错误,最近的 reject 会执行
            // reject 执行后默认下一个 then 会接收 undefined 
          console.log(err);
        })
        .then(data => {
            // 上一个 then 中失败没有返回值,默认为 undefined 
            console.log('resolve');        
          }, err => {
            console.log('reject');
        });
      
      1. 示例三:使用catch代替err
       read('./file-01.txt', 'utf8')
          .then(data => {
              return read(data, 'utf8');
          })
        .then(data => {
            return [data];
        })
        .then(data => {
            console.log(data);
        })
        .then(data => {
          console.log(data);
            // 抛出错误后,找到最近的接收错误方法
            // 如果所有的 then 都没有 reject 方法,则找最后一个 catch
            throw new Error('xxx');
          })
        .then(null)
        .then(data => {
            console.log('resolve');        
          })
        .catch(err => {
            console.log(err);
        });
      
  4. async/await
    • async/await是基于Promise实现的,不能用于普通的回调函数。
    • 和Promise一样,是非阻塞的。
    • 使异步代码看起来像是同步代码。
    • 一个函数如果加上async,那么该函数就会返回一个Promise,(如果指定的返回值不是Promise对象,也返回一个Promise,只不过立即 resolve ,处理方式同 then 方法,因此 async 函数通过 return 返回的值,会成为 then 方法中回调函数的参数。单独一个 async 函数,其实与Promise执行的功能是一样的。
    • await 就是异步等待,它等待的是一个Promise,因此 await 后面应该写一个Promise对象,如果不是Promise对象,那么会被转成一个立即 resolve 的Promise
    1. 实例一
        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实例,
            //await返回的结果直接return的结果,不需要进行新的then操作。
            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. 示例二
    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. 示例三
    async function func() {
        try {
            const num1 = await 200;
            console.log(`num1 is ${num1}`);
            const num2 = await Promise.reject('num2 is wrong!');
            console.log(`num2 is ${num2}`);
            const num3 = await num2 + 100;
            console.log(`num3 is ${num3}`);
        } catch (error) {
            console.log(error);
        }
     }
    
    func();
    // num1 is 200
    // 出错了
    // num2 is wrong!
    

参考:
- Javascript异步编程的4种方法
- 异步方法的发展流程
- JS 异步编程六种方案

你可能感兴趣的:(Javascript 异步编程方法汇总)