js是单线程的,一次只能完成一个任务,如果有多个任务,就需要排队,如果有一个任务耗时很长,那么后边任务就需要等待。为了解决这个问题,js将任务的执行分成两种模式:同步和异步
同步:会阻塞后面程序的运行
“同步模式”就是传统做法,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的。这往往用于一些简单的、快速的、不涉及 IO 读写的操作。
console.log(100)
alert(200) //只有点击了确认按钮,后面的程序才会接着执行
console.log(300);
异步:不会阻塞后面程序的运行
“异步模式”将每一个任务分成两段,第一段代码包含对外部数据的请求,第二段代码被写成一个回调函数,包含了对外部数据的处理。第一段代码执行完,不是立刻执行第二段代码,而是将程序的执行权交给第二个任务。等到外部数据返回了,再由系统通知执行第二段代码。所以,程序的执行顺序与任务的排列顺序是不一致的、异步的
console.log(100)
setTimeout(function(){
console.log(200)
},1000)
console.log(300) //输出100 300 200
什么情况下需要异步:需要等待的情况下
使用异步的场景:
定时任务:setTimeout , setInterval
网络请求:ajax请求,动态img加载
事件绑定
js是怎么实现异步的:事件循环(event-loop)
js将所有任务分为两类:同步任务和异步任务。
同步任务添加到主线程中,所有的同步任务在主线程上排队执行,形成一个执行栈,只有前一个任务执行完毕,才能执行后一个任务。异步任务指的是,不进入主线程,会将异步任务添加到事件线程中,当满足事件触发条件后,将任务添加到事件队列(event-queue)中。
事件队列中的任务何时被执行?
- 执行栈中的同步任务执行完毕,即主线程空闲时,此时系统会去查看任务队列,如果有可运行的异步任务就将任务添加到执行栈中开始执行。
- 如果任务队列中有多个异步任务待执行,那么这些异步任务也要排队等待被执行,并不是添加到任务队列中的异步任务就会立即执行。
只要主线程空了,就会去读取任务队列,这个过程是循环不断的,这就是javaScript的运行机制,也称作事件循环。
总结:
事件循环:同步的代码直接执行,异步的函数先放在异步队列中,待同步函数执行完毕后,再轮询执行异步队列的函数
一般说:100ms后会执行setTimeout里的回调函数,这样的说法并不严谨,
准确的说是:第一个setTimeout在100ms后才会被放到异步队列中,第二个setTimeout会立刻放入异步队列,而添加到异步队列的任务,只有等到主线程执行栈中的同步任务全部执行完毕之后,才会被执行,如果主线程执行任务很多,执行时间超过100ms,那么这个函数只能等待。
输出结果3 2 1,先进的先出。
解决异步的五种方法:
1.回调函数(callback)
异步编程最基本的方法:
$.ajax(url,()=>{
//do something...
$.ajax(url1,()=>{
//do something...
$.ajax(url2,()=>{
//do something...
})
})
})
存在的问题:回调地狱,多个回调嵌套,各个部分之间高度耦合,使得程序结构混乱,流程难以追踪,不能使用try…catch…捕获错误,不能return。
2.事件监听
另一种思路是采用事件驱动模式,任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
什么是事件驱动模式?
js是采用事件驱动(event-driven)响应用户操作的。
比如:通过鼠标或者按键在浏览器窗口或者网页元素(按钮,文本框…)上执行的操作,我们称之为事件(event);
由鼠标或热键引发的一连串程序的动作,称之为事件驱动(event-driver);
对事件进行处理的程序或函数,我们称之为事件处理程序(event handler);
3.Promise
Promise对象是CommonJS工作组提出的一种规范,目的是为异步编程提供统一接口。
Promise的思想是:每一个异步任务立刻返回一个promise对象,由于是立刻返回,所以可以采用同步的操作流程,这个promise对象有一个then方法,允许指定回调函数,在异步任务完成后调用。
Promise的三种状态:
Pending——Promise实例创建时的初始状态。
Fulfilled——成功状态
Rejected——失败状态
Pending状态可以转换为Fulfilled状态或者Rejected状态,但是Fulfilled状态或Rejected状态不能转换为Pending状态,也就是说,这种状态转换是不可逆的。
ES6提供了原生的Promise构造函数,用来生成Promise实例:
var promise = new Promise(function(resolve, reject) {
// 异步操作的代码
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise实例生成以后,可以用then方法分别指定Resolved状态和Reject状态的回调函数。then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。
4.async函数
5.generator函数
多任务的解决方案,协程(coroutine),意思是多个线程互相协作,完成异步任务。
generator函数是协程在ES6中的实现,最大特点是可以交出函数的执行权。整个generator函数就是一个异步任务的容器,异步操作需要暂停的地方,都用yield语句注明。
形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。
yield表达式只能用在 Generator 函数里面,用在其他地方都会报错。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
以上代码定义了一个generator函数helloWorldGenerator,内部有两个yield表达式(“hello” “world”),和一个return语句(结束执行)。
调用helloWorldGenerator函数后,并不执行,而是返回一个遍历器对象(lterator),它是一个指向内部状态的指针。
然后可以调用遍历器对象的next()方法,每次调用该方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield语句或return语句为止。
换言之,generator函数是分段执行的,yield表达式是暂停执行的标记,next方法可以恢复执行。
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true
hw.next()
// { value: undefined, done: true }
第一次调用,Generator 函数开始执行,直到遇到第一个yield表达式为止。next方法返回一个对象,它的value属性就是当前yield表达式的值hello,done属性的值false,表示遍历还没有结束。
第二次调用,Generator 函数从上次yield表达式停下的地方,一直执行到下一个yield表达式。next方法返回的对象的value属性就是当前yield表达式的值world,done属性的值false,表示遍历还没有结束。
第三次调用,Generator 函数从上次yield表达式停下的地方,一直执行到return语句(如果没有return语句,就执行到函数结束)。next方法返回的对象的value属性,就是紧跟在return语句后面的表达式的值(如果没有return语句,则value属性的值为undefined),done属性的值true,表示遍历已经结束。
第四次调用,此时 Generator 函数已经运行完毕,next方法返回对象的value属性为undefined,done属性为true。以后再调用next方法,返回的都是这个值。
总结一下,调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。
generator函数和async函数的区别
1、generator函数被调用后返回一个遍历器对象(lterator),async函数返回一个promise对象
2、async函数内置执行器,可以像普通函数那样调用,generator函数需要使用co模块来实现流程控制或者自定义流程控制。
3、async是generator的语法糖