async await 了解

问题引出:

思考(案例来自stackoverflow):

function foo(){
    var result;
    $ajax({
        url:'...',
        success:function(response){
            result=response;
            //return response;//tried this one as well
        }
    });
    return result;
}
var result=foo();

初学异步的时候,这里是很容易错的地方,你想要获取从服务器端返回的数据,结果却一直undefined。

!!!气死我了


在弄清楚此概念之前,先了解 JS 的异步机制:

参考链接:https://juejin.im/post/5a6ad46ef265da3e513352c8

https://segmentfault.com/a/1190000013141641

https://segmentfault.com/a/1190000004322358

http://www.ruanyifeng.com/blog/2015/05/async.html

首先先了解什么是同步和异步

同步:

 在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;就像上面的图中一样,线性执行

异步:

如果函数是异步的,这个函数在被调用的时候,会马上返回一个结果,但是这个结果可能不是预期的哦。(这是因为这个函数还没有得到最终的结果,但是又不能让人家一直等着,所以就先返回一个结果,导致不阻塞住)。等到这个函数终于知道结果了,如果有调用他的,他才告诉人家正确的结果。

下面以AJAX请求为例,来看一下同步和异步的区别:

  • 同步ajax

主线程:“你好,AJAX线程。请你帮我发个HTTP请求吧,我把请求地址和参数都给你了。”

AJAX线程:“......”

主线程::“喂,AJAX线程,你怎么不说话?”

AJAX线程:“......”

主线程::“喂!喂喂喂!”

AJAX线程:“......”

(一炷香的时间后)

主线程::“喂!求你说句话吧!”

AJAX线程:“主线程,不好意思,我在工作的时候不能说话。你的请求已经发完了,拿到响应数据了,给你。”

  • 异步ajax

主线程:“你好,AJAX线程。请你帮我发个HTTP请求吧,我把请求地址和参数都给你了。”

AJAX线程:“好的,主线程。我马上去发,但可能要花点儿时间呢,你可以先去忙别的。”

主线程::“谢谢,你拿到响应后告诉我一声啊。”

(接着,主线程做其他事情去了。一顿饭的时间后,它收到了响应到达的通知。)

 单线程语多线程、JS 单线程:

在上面介绍异步的过程中就可能会纳闷:既然JavaScript是单线程,怎么还存在异步,那些耗时操作到底交给谁去执行了?

JavaScript其实就是一门语言,说是单线程还是多线程得结合具体运行环境。JS的运行通常是在浏览器中进行的,具体由JS引擎去解析和运行。下面我们来具体了解一下浏览器。

浏览器:

目前最为流行的浏览器为:Chrome,IE,Safari,FireFox,Opera。浏览器的内核是多线程的。

一个浏览器通常由以下几个常驻的线程:

  • 渲染引擎线程:顾名思义,该线程负责页面的渲染
  • JS引擎线程:负责JS的解析和执行
  • 定时触发器线程:处理定时事件,比如setTimeout, setInterval
  • 事件触发线程:处理DOM事件
  • 异步http请求线程:处理http请求

需要注意的是,渲染线程和JS引擎线程是不能同时进行的。渲染线程在执行任务的时候,JS引擎线程会被挂起。因为JS可以操作DOM,若在渲染中JS处理了DOM,浏览器可能就不知所措了。

JS引擎:

通常讲到浏览器的时候,我们会说到两个引擎:渲染引擎和JS引擎。渲染引擎就是如何渲染页面,Chrome/Safari/Opera用的是Webkit引擎,IE用的是Trident引擎,FireFox用的是Gecko引擎。不同的引擎对同一个样式的实现不一致,就导致了经常被人诟病的浏览器样式兼容性问题。这里我们不做具体讨论。

JS引擎可以说是JS虚拟机,负责JS代码的解析和执行。通常包括以下几个步骤:

  • 词法分析:将源代码分解为有意义的分词
  • 语法分析:用语法分析器将分词解析成语法树
  • 代码生成:生成机器能运行的代码
  • 代码执行

不同浏览器的JS引擎也各不相同,Chrome用的是V8,FireFox用的是SpiderMonkey,Safari用的是JavaScriptCore,IE用的是Chakra。

之所以说JavaScript是单线程,就是因为浏览器在运行时只开启了一个JS引擎线程来解析和执行JS。那为什么只有一个引擎呢?如果同时有两个线程去操作DOM,浏览器是不是又要不知所措了。

所以,虽然JavaScript是单线程的,可是浏览器内部不是单线程的。一些I/O操作、定时器的计时和事件监听(click, keydown...)等都是由浏览器提供的其他线程来完成的。

我们常说“JavaScript是单线程的”。所谓单线程,是指在JS引擎中负责解释和执行JavaScript代码的线程只有一个。不妨叫它主线程。

但是实际上还存在其他的线程。例如:处理AJAX请求的线程、处理DOM事件的线程、定时器线程、读写文件的线程(例如在Node.js中)等等。这些线程可能存在于JS引擎之内,也可能存在于JS引擎之外,在此我们不做区分。不妨叫它们工作线程。

正是由于JavaScript是单线程的,而异步容易实现非阻塞,所以在JavaScript中对于耗时的操作或者时间不确定的操作,使用异步就成了必然的选择。

 
 
解决办法:
1. 通过回调函数解决
function foo(callback){//定义函数的时候将另一个函数(回调函数)作为参数传入定义的函数中。
    $ajax({
        //...
        success:callback//异步操作执行完毕后,再执行该回调函数,确保回调在异步操作之后执行。
    });
}
function myCallback(result){
    //...
}
foo(myCallback);
 
上面一段代码解释: 我们定义了两个函数 myCallback 和 foo。
  foo 函数里面呢,是有异步操作的(ajax),按照正常情况,他会返回一个不是真正结果的值,那我们怎么样才能得到真正的结果呢?就是当这个函数执行完的时候,调用 myCallback(这个函数是通过参数传进来的),得到这个真正的结果,就解决了这个问题。
优点:比较容易理解;
缺点:1.高耦合,维护困难,回调地狱; 2.每个任务只能指定一个回调函数; 3.如果几个异步操作之间并没有顺序之分,同样也要等待上一个操作执行结束再进行下一个操作。
 
2. Promise
 

先看上面这个函数是什么意思: ajax 函数执行,然后是两个 then 方法和一个 catch 方法 =》 ajax 里面是啥呢? 是返回了一个 Promise 的东西 =》 Promise 里面是啥呢? 是一系列函数执行,我也不知道这是执行的啥,反正就是在执行。

那就看看这个 Promise 是个什么东西?烦死了,弄的稀奇古怪的。

Promise代表了一个异步操作,可以将异步对象和回调函数脱离开来,通过.then方法在这个异步操作上绑定回调函数,Promise可以让我们通过链式调用的方法去解决回调嵌套的问题。先捋一下, 应该就是 Promise 里面的是一个异步操作,then 函数是在他执行完之后才回去挨个执行的,应该是这个意思吧,和上面那种比就是换了个写法嘛。

promise对象存在三种状态:
1)Fulfilled:成功状态
2)Rejected:失败状态
3)Pending:既不是成功也不是失败状态,可以理解为进行中状态

promise对象的两个重要方法:resolve/reject
1)resolve方法可以使Promise对象的状态改变为成功,同时传递一个参数用于后续成功后的操作。
2)reject方法可以将Promise对象的状态改变为失败,同时将错误信息传递到后续错误处理的操作。

.then可以使用链式调用,原因在于:每一次执行该方法时总会返回一个Promise对象(Promise 对象的意义就是告诉成功还是失败吧)。
另外,在then的函数当中的返回值,可以作为后续操作的参数(例如:.then(return a).then(console.log(a+b))),这不就是线性执行了嘛!!!

 那么问题来了,如果上面代码异步操作抛出错误,会怎么样?会调用catch方法指定的回调函数,处理这个错误,而且then方法指定的回调函数,如果运行中抛出错误,也会被catch捕获。Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止,也就是说,错误总是会被下一个catch语句捕获。
 

理解Promise用法的关键点:
1.then方法是Promise实例的方法,即Promise.prototype上的,它的作用是为Promise实例添加状态改变时的回调函数,这个方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。
2.链式中的第二个then开始,它们的resolve中的参数,是前一个then中resolve的return语句的返回值。
3.关于执行顺序:Promise在实例化的时候就会执行,也就是如果Promise的实例化语句中函数console.log输出语句,它会比then中的先执行。Promise.all中传入的Promise对象的数组(假设为p1、p2),即使p2的运行速度比p1快,Promise.all方法仍然会按照数组中的顺序将结果返回。
理解了上面这些方便写原生的Promise,利用观察者模式。后面补充。

Promise的缺点:
1.当处于未完成状态时,无法确定目前处于哪一阶段。
2.如果不设置回调函数,Promise内部的错误不会反映到外部。
3.无法取消Promise,一旦新建它就会立即执行,无法中途取消。

 

3.async/await:

很多人说async/await是异步编程的终极解决方案、
JavaScript 的 async/await 实现,离不开 Promise。

 

看上面这段代码: 有一个 delay 函数,里面有Promise关键字(表明是异步操作,大家让让,我先执行)。 在看 getAllBooks 函数, 它里面有 await 这个关键字(意如其名,大家等等,等下面这个执行完了才能执行别的)

上面的 delay() 没有申明为 async。实际上,delay() 本身就是返回的 Promise 对象,加不加 async 结果都一样。

只要在函数名之前加上async关键字,就表明这个函数内部有异步操作。这个异步操作返回一个Promise对象,前面用await关键字注明。函数执行的时候,一旦遇到await,就会先执行await后面的表达式中的内容(异步),不再执行函数体后面的语句。等到异步操作执行完毕后,再自动返回到函数体内,继续执行函数体后面的语句。

 async:定义异步函数

1)自动把函数转换为Promise
2)当调用异步函数时,函数返回值会被resolve处理
3)异步函数内部可以使用await

await:暂停异步函数的执行
1)当使用在Promise前面时,await等待Promise完成,并返回Promise的结果
2)await只能和Promise一起使用,不能和callback一起使用
3)await只能用在async函数中

async/await并不会取代promise,因为async/await底层依然使用promise。

每次遇到 await 关键字时,Promise 都会停下在,await 把异步变成了同步。

猪猪侠要努力呀!
原文地址:https://www.cnblogs.com/mlllily/p/10983762.html