浅谈下JavaScript中的异步编程

220 阅读5分钟

在工作中经常遇到:需要拿到接口中的数据,而不是自己初始化在data中的数据。或者换一种思路,怎么在同步代码中拿到异步数据?

你先得弄明白js中的事件执行机制:

主线程执行的任务,称为同步任务,特点是:排队执行,只有前一个任务执行完毕,才能进行下一个任务

任务队列执行的任务,称为异步任务,特点是:当异步任务到时间可以执行时,任务队列通知主线程,这一任务才会在主线程中排队等待执行

执行流程同步任务主线程中执行,形成一个执行栈,执行完毕后;系统会读取任务队列排队的异步任务,而之前排队的任务队列中的异步任务结束等待状态,进入执行栈开始执行剩下的任务。

一个非常简单的应用场景:

    let time
    setTimeout(() => {
      time = 60
      console.log(time); // 60
    }, 2000);
    console.log(time); // undefined

思考下,怎么得到异步的结果 ?

1、回调callback()来实现,

让我们研究下这个setTimeout函数,setTimeout函数的常见语法:var timeoutID = scope.setTimeout(function[ , delay, arg1, arg2, ...]),例子如下:

    let timeId = setTimeout((a,b,c) => {
      console.log(a,b,c);
    }, 1000,1,2,3);
    let timeId2 = setTimeout((a,b,c) => {
      console.log(a,b,c);
    }, 1000,1,2,3);
    let timeId3 = setInterval((a,b,c) => {
      console.log(a,b,c);
    }, 2000,1,2,3);
    console.log(timeId,'timeId'); 
    console.log(timeId2,'timeId2'); 
    console.log(timeId3,'timeId3');

Tips: 执行结果会同步打印出 :1 'timeId' 、2 'timeId2' 、3 'timeId3' ;接着过1秒异步同步化执行 1 2 3 、1 2 3;再过1秒执行1 2 3;接着每隔2秒执行 1 2 3。函数返回值是一个定时器id(或者计时器id)且是一个正整数,验证了setTimeout() 和 setInterval() 共用一个id“编号池”。值得一提的是,这个延迟的时间并不准确,存在最小延迟时间。 这里留个问题,有兴趣可以研究下为什么存在这个延迟时间还有如何解决这个问题?

setTimeout()中第一个参数是个函数,也称高阶函数,但更习惯叫它回调函数。

      function fun(fn){
        fn(0)
        setTimeout(() => {
          fn(1)
          setTimeout(() => {
            fn(2)
          }, 1000);
        }, 1000);
      }
      fun((val)=>{
        console.log(val);
      })

Tips: 执行结果会同步打印出 :0;过1秒后打印:1;再过1秒后打印:2。

      let num  = {a:1}
      function fun(fn){
        fn(0)
        setTimeout(function(){
          fn(this)
        }.bind(num), 1000);
      }
      fun((val)=>{
        console.log(val);
      })

Tips: 执行结果会同步打印出 :0;过1秒后打印:{a:1}。 因为箭头函数中没有this的指向,所以把 setTimeout的回调函数改成箭头函数则不能使用强绑定来写会报错!上面的代码也说明了bind的独特性:并没有立即执行函数,若改成call和apply,执行结果会变成同步打印 :0、{a:1}。

回归正题;

    function fun(fn){
      setTimeout(() => {
        fn(2)
      }, 2000);
    }
    fun((res)=>{
      console.log(res);
    })

Tips: 执行结果会异步2秒后打印出:2。即:用回调函数拿到异步数据。

2、链式调用.then()

    // 写法1
    let p = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(2)
      }, 2000);
    })
    p.then(res=>{
      console.log(res);
    })
    
    // 写法2
    let a = 1
    function getData() {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          let a = 10
          resolve(a)
        }, 2000);
      })
    }
    getData().then(res=>{
      console.log(res,'resssss');
    })

Tips: 执行结果会异步2秒后打印出:2。即:用链式调用拿到异步数据。

3、async和await

    // 写法1
    let p = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(2)
      }, 2000);
    });
    // IIFE
    (async function() {
      let res = await p
      console.log(res);
    })()
    
    // 写法2
    let a = 1
    function getData() {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          let a = 10
          resolve(a)
        }, 2000);
      })
    }
    !async function () {
      let res = await getData()
      console.log(res, 'ress');
    }()

Tips: 执行结果会异步2秒后打印出:2。即:用async、await拿到异步数据。值得注意的是,在立即执行函数(IIFE)前面的分号如果忘记写了,就报错!!!

番外篇:模拟后端异步数据

分别用回调函数、then()、async模拟接口返回的数据,怎么处理?

回调函数写法

    function getData(params) {
      return function (callback) {
        data = {
          code: 200,
          type: 'Get',
          message: '访问成功',
          id: params.id,
          data: [{
              name: '武汉市',
              time: '2021',
              number: 2000
            },
            {
              name: '宜昌市',
              time: '2022',
              number: 1000
            },
          ]
        }
        setTimeout(() => {
          callback(data)
        }, 2000);
      }
    }
    function conduct() {
      let params = {
        id: 100
      }
      // 回调函数写法
      getData(params)(res => {
        console.log(res); // {code: 200, type: 'Get', message: '访问成功', id: 100, data: Array(2)}
      })
    }
    conduct()

then写法

    function getData(params) {
      return new Promise((resolve, reject) => {
        let data = {
          code: 200,
          type: 'Get',
          message: '访问成功',
          id: params.id,
          data: [{
              name: '武汉市',
              time: '2021',
              number: 2000
            },
            {
              name: '宜昌市',
              time: '2022',
              number: 1000
            },
          ]
        }
        setTimeout(() => {
          resolve(data)
        }, 2000);
      })
    }
    
    function conduct() {
      let params = {
        id: 100
      }
      // then写法
      getData(params).then(res => {
        console.log(res); // {code: 200, type: 'Get', message: '访问成功', id: 100, data: Array(2)}
      })
    }
    conduct()

async写法

    function getData(params) {
      return new Promise((resolve, reject) => {
        let data = {
          code: 200,
          type: 'Get',
          message: '访问成功',
          id: params.id,
          data: [{
              name: '武汉市',
              time: '2021',
              number: 2000
            },
            {
              name: '宜昌市',
              time: '2022',
              number: 1000
            },
          ]
        }
        setTimeout(() => {
          resolve(data)
        }, 2000);
      })
    }
    // async写法
    async function conduct() {
      let params = {
        id: 100
      }
      let res = await getData(params)
      console.log(res); // {code: 200, type: 'Get', message: '访问成功', id: 100, data: Array(2)}
    }
    conduct()

突然让我联想到,我自己经常犯的理解误区,Dom机制和异步编程的基本逻辑混淆,这个边缘知识点,以后记起来再进行补充说明!

以上就是,我对JavaScript开发过程中的异步编程里面的一点理解!