js异步处理

105 阅读7分钟

Es5异步处理

所谓异步,就是代码执行的顺序并不是按照从上到下的顺序一次性一次执行,而是在不同的时间段执行,一部分代码在“未来执行”。

setTimeout( ()={ console.log('1') },1000)

      console.log("2")

回调函数

回调函数我们都比较熟悉。如果有两个函数f1和f2,其中f1是异步函数,而f2是同步函数,如何让f2函数在f1之后执行?答案就是回调函数。
幸运的是,基本绝大多数的异步操作都会给我们提供一个加入回调函数的接口,我们可以传入一个函数,在异步操作结束后"立即”调用(为什么加引号在异步操作的机制中会探讨)。
我们之前所举的例子中 ,setTimeout( ()=>{ console.log(‘1’) },1000) 就是往异步函数setTimeout中传入回调函数,在计时1秒后调用。
再比如我们常用的ajax:

    function f2(){

        console.log("我是回调函数")

     }

  $.ajax("www.baidu.com") //在百度页面打开控制台运行,省的跨域

  f2()//我是回调函数  f2先执行//undifined   

$.ajax("www.baidu.com",{success:f2})

在ajax获取数据后调用f2。

异步操作的机制

异步操作时浏览器(或者其他环境)是如何工作的?回调函数在何时调用,如何调用?我们通过异步操作的机制的学习都能得到答案。
在主程序结束和回调函数调用期间,是没有JS代码运行的,这是否意味着此时我们的浏览器是停止工作的?当然不是。
其实,绝大多数异步操作的功能并不是完全由JS完成的,JS引擎不会计时,也不会发送和接受网络请求,这一切都是由浏览器(或者Node等工作环境)来完成的,js引擎只是通过调用浏览器给它的接口,"命令"浏览器完成这些功能。所以异步操作不代表不工作,只是该工作转交给浏览器处理 ,当浏览器处理完这部分操作后会"告诉"JS引擎来调用回调函数。
然而这个"告诉"并不是我们想象的那样是浏览器主动对JS引擎说"我已经处理好了",而是JS引擎每隔一段时间就问浏览器"你有没有完成操作",一旦完成,就调用回调函数。这个过程不断重复就叫做Event Loop,如下图所示:\

a.png

当进程中的JavaSrcipt代码执行结束后,会检查任务队列中是否有任务,如果有任务,就立刻执行,如果没有任务,就过一段时间再次检查,循环往复。
任务队列又是什么呢?浏览器每次完成一个异步任务,就会在一个队列(先进先出)中插入回调函数, 一旦EventLoop中有内容,并且此时没有JS程序运行,JS引擎就会调用该队列的第一个回调函数 ;但是如果此时有JS程序正在运行,需要等该JS代码运行完,然后才会依次调用任务队列中的回调函数。
然后我们也就可以解释下面这个代码了:

   setTimeout('console.log("1")',0)

    console.log('2')

输一下,结果居然是先输出2,再输出1,setTimeout不是设时间为0么,为什么没有立刻执行?其实计时器虽然设为0,会立即在任务队列中加入console.log(“1”),但是此时还有代码在运行,所以不能执行任务队列中的任务,等console.log(‘2’)执行完毕,主程序执行完,才会调用任务队列中的console.log(1)。
接下来来个更加详细的介绍图:

b.png 我来解释一下,首先是左侧JS一栏,代码运行时,把代码加载到一个栈(stack)中,按照顺序执行,并且将数据存在堆(heap)中,要使用时调用。
然后:

1.在代码执行过程中,一旦遇到异步操作,会调用浏览器提供给JS引擎的接口(WebAPIS),让浏览器处理DOM、ajax、setTimeout等操作,然后JS引擎继续处理下面的JS,两者间的工作互不干扰。(接下来的2和3是同时进行的,部分先后)

2.JS引擎把JS代码加载完毕,就检查任务列表中是否有任务要处理,有的话就执行,没有的话,等待一会再检查。

3.浏览器一旦把异步操作处理完毕,就在任务队列中加入onClick,onLoad回调函数等 。 (注意这里不是具体传入的函数,而是onClick,onLoad,但是onClick、onLoad执行时会根据作用域等在堆中找到相应的函数值,然后执行)
.

4.重复上述过程
最后再来个例子热热身:下面两行代码运行结果是否有区别

var req = new XMLHttpRequest( );

req.open(‘GET’ ,url);

req. = function(){};

req. = function(){};;

req.send()

var req = new XMLHttpRequest( );

req.open('GET' ,url);

req..send();

req. = function(){};

req. = function(){};;

 

答案是肯定没有的。
因为req.send是异步操作,在操作完成后,不能立即调用和,而是要等到主程序执行完毕,此时在上面两端代码中,两个函数都完成赋值。

 

Es6处理*

同步异步

对于同步异步的概念先用白话简单的介绍一下。
同步:操作的执行有先后顺序,需要按顺序一个一个的执行。
异步:多个操作可以同时执行,且互不干扰。一般多用于数据交互。
相对比同步处理,异步处理性能好,效率高,用户体验也较好,但是之前异步操作都是通过回调函数来实现的,写起来比较复杂,可读性也较差。ES6中promise的出现,就是为了可以让我们更好,更清晰的写异步处理的代码

promise

promise是ES6中的一个对象,用来对异步操作做一个统一的封装。下面通过一段代码来看一下promise是如何使用的。

00001. 

let promise = new Promise((resolve, reject) => {

00002. 

00003. 

     // 异步操作代码

00004. 

00005. 

    $.ajax({

00006. 

00007. 

        url: 'data.json',

00008. 

00009. 

        dataType: 'json',

00010. 

00011. 

        success(data) {

00012. 

00013. 

            resolve(data);

00014. 

00015. 

        },

00016. 

00017. 

        error(err) {

00018. 

00019. 

            reject(err);

00020. 

00021. 

        }

 

    })

});

 

p.then(data => {

 

    alert('success');

 

}, res => {

 

    alert('failed');

 

});

00022. 

首选需要先new一个Promise对象的实例,它的构造函数支持传入一个函数作为参数,这个函数中就是你要写的异步处理的代码。

这个函数有两个参数,分别是resolve和reject。resolve是在异步操作成功时调用,并将异步操作的结果,作为参数传递出去。reject在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

promise.then()是在异步操作处理完成后调用,它支持传入两个函数作为参数,第一个函数是在操作成功后执行,第二个函数是在操作失败后执行。其实这个两个函数就是上面promise中的resolve和reject,它不是promise自带的,而是你自己编写的。

在页面渲染的时候,我们需要多条数据,每次请求一个数据都要写一个promise未免有些太麻烦,有什么简单的办法呢?Promise.all()来帮你实现。

Promise.all()

Promise对象另外一个强大之处就是Promise.all方法,它用于将多个Promise实例,包装成一个新的Promise实例。

 

Promise.all([

 

     //jquery封装的ajax返回的是一个promise

 

    $.ajax({url: 'data1.json', dataType: 'json'});

00001. 

00002. 

    $.ajax({url: 'data2.json', dataType: 'json'});

00003. 

00004. 

    $.ajax({url: 'data3.json', dataType: 'json'});

00005. 

00006. 

]).then((data) => {

00007. 

00008. 

    alert('success');

00009. 

00010. 

    console.log(data); // [data1, data2, data3]

00011. 

00012. 

}, (err) => {

00013. 

00014. 

    alert('failed');

00015. 

00016. 

})

 

Promise.all()传入的是一个promise对象的数组,只有当其中所有的promise对象都执行完成后,才会执行then()函数。都成功后执行resolve函数,它的参数是一个数组,包含每个promise对象成功后返回的结果。要是有一个promise对象执行失败,就会执行reject函数。

虽然Promise.all()可以将我们需要的多个数据封装成一个promise对象来请求,但是还存在一个问题,如果这多个promise对象中有逻辑关系该怎么办?比如我们拿到第一个promise的对象返回的结果--用户的信息,来判断是否需要获取会员数据(第二个promise中的代码)。

很显然,这时候Promise.all()无法满足我们的需要,所以async和await出现了。

async和await

async和await的强大之处就是你完全可以用同步处理代码的写法来写异步处理的代码。既有异步处理的高性能和好的用户体验,又可以使代码简洁易读。下面通过一段代码,来感受一下它的强大。

00001. 

async function getData() {

00002. 

00003. 

    let data1 = await $.ajax({url: 'data1.json', dataType: 'json'});

00004. 

00005. 

    if(data1) {

00006. 

00007. 

        let data2 = await $.ajax({url: 'data2.json', dataType: 'json'});

00008. 

00009. 

    } else {

00010. 

00011. 

        let data3 = await $.ajax({url: 'data3.json', dataType: 'json'});

00012. 

00013. 

    }

00014. 

00015. 

}

 

00016. 

async 放在函数声明之前,在这个函数中如果有异步处理,就使用await。await 之后必须是一个promise,它会在这个异步处理执行完成后,再执行下面的操作。这时候,如果你想要处理一些逻辑关系就非常简单了。

注:  async 和 await只是把异步的操作用同步的写法写,但是背后的逻辑还是异步的,它只是一个语法糖。在执行时,它还是会被编译成类下面代码的形式。

 


 

\