问题引出:
思考(案例来自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 的异步机制:
参考链接:
首先先了解什么是同步和异步:
同步:
在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;就像上面的图中一样,线性执行
异步:
如果函数是异步的,这个函数在被调用的时候,会马上返回一个结果,但是这个结果可能不是预期的哦。(这是因为这个函数还没有得到最终的结果,但是又不能让人家一直等着,所以就先返回一个结果,导致不阻塞住)。等到这个函数终于知道结果了,如果有调用他的,他才告诉人家正确的结果。
下面以AJAX请求为例,来看一下同步和异步的区别:
- 同步ajax
主线程:“你好,AJAX线程。请你帮我发个HTTP请求吧,我把请求地址和参数都给你了。”
AJAX线程:“......”
主线程::“喂,AJAX线程,你怎么不说话?”
AJAX线程:“......”
主线程::“喂!喂喂喂!”
AJAX线程:“......”
(一炷香的时间后)
主线程::“喂!求你说句话吧!”
AJAX线程:“主线程,不好意思,我在工作的时候不能说话。你的请求已经发完了,拿到响应数据了,给你。”
- 异步ajax
主线程:“你好,AJAX线程。请你帮我发个HTTP请求吧,我把请求地址和参数都给你了。”
AJAX线程:“好的,主线程。我马上去发,但可能要花点儿时间呢,你可以先去忙别的。”
主线程::“谢谢,你拿到响应后告诉我一声啊。”
(接着,主线程做其他事情去了。一顿饭的时间后,它收到了响应到达的通知。)
单线程语多线程、JS 单线程:
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中对于耗时的操作或者时间不确定的操作,使用异步就成了必然的选择。
function foo(callback){ //定义函数的时候将另一个函数(回调函数)作为参数传入定义的函数中。 $ajax({ //... success:callback//异步操作执行完毕后,再执行该回调函数,确保回调在异步操作之后执行。 }); } function myCallback(result){ //... } foo(myCallback);
先看上面这个函数是什么意思: 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))),这不就是线性执行了嘛!!!理解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)异步函数内部可以使用awaitawait:暂停异步函数的执行
1)当使用在Promise前面时,await等待Promise完成,并返回Promise的结果2)await只能和Promise一起使用,不能和callback一起使用3)await只能用在async函数中async/await并不会取代promise,因为async/await底层依然使用promise。
每次遇到 await 关键字时,Promise 都会停下在,await 把异步变成了同步。