异步方式总结

写在前面

阮一峰老师有一篇文章关于事件队列的,里面算是比较详细地讲解了同步与异步。

阮一峰的讲解

从这篇文章中,我们能认识到,所谓的同步与异步。

那么我们自己总结下:

JavaScript语言最大的特点,单线程语言。为什么单线程?因为效率高。
如果是多线程呢,一个线程在DOM节点上添加了内容,另一个线程同时在删除了节点。浏览器懵逼,不知道按照哪个线程为基准去执行。所以这就是JavaScript是单线程的原因。
既然是单线程的,所以就必须按照单线程的规矩来了。单线程的规矩是什么?一个任务执行完了,在继续执行下一个任务。这就是单线程的规矩,我的地盘我做主。
可有的时候,这样也会出现问题。比如从服务器请求数据,如果这个请求数据任务完全按照规矩办事,服务器还特别慢,数据总是出不来,页面就一直停在那等着,别的功能也都用不了。这样用户肯定疯了。
所以,尽管是单线程,但是任务分为同步任务和异步任务。同步任务放在任务栈,异步任务放在任务队列。还是接着上边的例子来说,请求数据任务是异步任务,放在任务队列里边。同步任务是有高度的优先权的,必须先执行同步任务,所有的同步任务都执行完成,然后才会执行任务队列中的异步任务。

阮老师总结

  1. 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
  2. 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
  3. 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
  4. 主线程不断重复上面的第三步。

所以,我们所说的异步就是异步任务。在JavaScript中常用的常见的异步任务,无非有四种形式。

setTimeout

这个是最常见的,也是使用频率最高的。

<code class="language-js">setTimeout(() =&gt; {
    // callback
},200)
</code>

Ajax

Ajax请求也是异步任务。我们正好来回顾一下利用原生的手段,写一个ajax请求.

<code class="language-js">function createXhr() {
    if (window.XMLHttpRequest) {
        return new XMLHttpRequest();
    }else{
        return ActiveXObject('Microsoft.XMLHTTP');
    }
}
var xhr = createXhr();
xhr.open('get/post',url,true/false);
// true,异步
// false,同步
xhr.send(null);
xhr.onreadystatechange = function () {
    if (xhr.readystate==4) {
        if (xhr.status&gt;=200&amp;&amp;xhr.status&lt;300||xhr.status==304) {
            // xhr.responseText
            return JSON.parse(xhr.responseText);
        }else{
            console.log('请求结果:'+xhr.statusText)
        }
    }else{
        throw Error('请求出错')
    }
}
</code>

promise

<code class="language-js">// 一般的使用方式
function test(){
    return new Promise((resolve,reject) =&gt; {
        if (condition){
            resolve(data)
        }else{
            reject(data)
        }
    })
}
</code>

async/await

这个用的还真是不多,本篇的主要内容主要也是为了这个。

async

首先,需要知道async做了什么?看一下最简单的使用方式。

<code class="language-js">async function myTest(){
    return 'hello'
}
</code>

运行这个函数,你会得到什么?打印出来看看,就知道async到底做了什么。

<code class="language-js">console.log(myTest());
// 结果
// Promise对象。Promise {[[PromiseStatus]]: "resolved", [[PromiseValue]]: "hello"}
</code>

看过结果之后,有一种恍然大悟。原来async将普通函数的返回值变成了一个Promise对象。
既然是Promise对象,那么我们获取值的方式应该变为。

<code class="language-js">myTest().then(v =&gt; console.log(v))
</code>

await

接下来是await。顾名思义,等待。那await在等待什么,等来了结果又做什么?

await在等什么?

一般来说,都认为 await 是在等待一个 async 函数完成。不过按语法说明,await 等待的是一个表达式,这个表达式的计算结果是 Promise 对象或者其它值(换句话说,就是没有特殊限定)。

因为 async 函数返回一个 Promise 对象,所以 await 可以用于等待一个 async 函数的返回值——这也可以说是 await 在等 async 函数,但要清楚,它等的实际是一个返回值。注意到 await 不仅仅用于等 Promise 对象,它可以等任意表达式的结果,所以,await 后面实际是可以接普通函数调用或者直接量的。所以下面这个示例完全可以正确运行

<code class="language-js">// 普通函数
function waitResult1(){
    return 'result1'
}
// async函数
async function waitResult2(){
    return Promise.resolve('result2')
}
// 获取结果
async function getResult(){
    var r1 = await waitResult1();
    var r2 = await waitResult2();
    console.log(r1,r2)
}
// 运行函数,得到结果
getResult(); // result1,result2
</code>

await 等到了结果,然后?

await 等到了它要等的东西,一个 Promise 对象,或者其它值,然后呢?我不得不先说,await 是个运算符,用于组成表达式,await 表达式的运算结果取决于它等的东西。

如果它等到的不是一个 Promise 对象,那 await 表达式的运算结果就是它等到的东西。

如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果

看到上面的阻塞一词,心慌了吧……放心,这就是 await 必须用在 async 函数中的原因。async 函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个 Promise 对象中异步执行。

async/await帮我们做了什么

举个栗子

<code class="language-js">function takeTime(n) {
    return new Promise((resolve,reject) =&gt; {
        setTimeout(() =&gt; {
            resolve(n+200)
        }, n);
    })
}
function step1(n) {
    console.log(`step1 的参数 ${n}`)
    return takeTime(n)
}
// m这个参数是step1执行之后返回的数据
function step2(n,m) {
    console.log(`step2 的参数 ${n} and ${m} `)
    return takeTime(n+m)
}
// 同理l参数是step2执行之后返回的参数
function step3(n,m,l) {
    console.log(`step2 的参数 ${n} and ${m} and ${l} `)
    return takeTime(n+m+l)
}
</code>
<code class="language-js">// 如果使用async
async function doIt(params) {
    var time1 = 300,
        time2,
        time3,
        result;
    time2 = await step1(time1);
    time3 = await step2(time1,time2);
    result = await step3(time1,time2,time3)
}
doIt();
// 结果
// step1 的参数 300
// step2 的参数 300 and 500 
// step2 的参数 300 and 500 and 1000
</code>
<code class="language-js">// 如果使用Promise的方式
function doIt2() {
    var time1 = 300,
        time2,
        time3,
        result;
        step1(time1)
        .then(time_2 =&gt; {
            time2 = time_2;
            return step2(time1,time2)
        })
        .then(time3 =&gt; {
            return step3(time1,time2,time3)
        })
}
doIt2();
</code>

可以看到,使用await的写法更加简洁一些,更具有逻辑性,代码的可读性更高。而使用then的链式操作,传递参数这块,略显麻烦。

发表评论

电子邮件地址不会被公开。 必填项已用*标注