异步
同步
- 指在 主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务
- 也就是调用一旦开始,必须这个调用 返回结果(划重点——)才能继续往后执行。程序的执行顺序和任务排列顺序是一致的。
异步
- 异步任务是指不进入主线程,而进入 任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程。
- 程序的执行顺序和任务的排列顺序是不一致的。
- 用的setTimeout和setInterval函数,Ajax都是异步操作
实现异步的方法
- 回调函数(Callback)、事件监听、发布订阅、Promise/A+、生成器Generators/ yield、async/await
Promise
- ES6中的Promise 是异步编程的一种方案。从语法上讲,Promise 是一个对象,它可以获取异步操作的消息。
- Promise对象, 可以将异步操作以同步的流程表达出来。使用 Promise 主要有以下好处:
- 可以很好地解决回调地狱的问题
- 语法非常简洁。Promise 对象提供了简洁的API,使得控制异步操作更加容易。
- 状态的表现
- pending状态,不会触发then和catch
- resolved状态,会触发后续的then(onResloved)回调函数
- rejected状态,会触发then(null, onRejected),没有则触发后续的catch回调函数
- then和catch改变状态
- then正常返回resolved,里面报错则返回rejected
- catch正常也返回resolved,里面报错则返回rejected
- catch是一个语法糖,相当于调用Prmise.prototype.then(null, onRejected)
- Promise.all(iterable)
- 语法:var p = Promise.all([p1, p2, p3]);
- Promise.all方法接受一个数组作为参数,p1、p2、p3都是Promise实例,如果不是,就会先调用Promise.resolve方法,将参数转为Promise实例,再进一步处理。(Promise.all方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。)
- (1) 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
- (2) 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
- Promise.race(iterable)
- 语法:var p = Promise.race([p1, p2, p3]);
- Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的回调函数。
- Promise.race方法的参数与Promise.all方法一样,如果不是Promise实例,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理。
defer和async
- 区别主要在于一个执行时间,defer会在文档解析完之后执行,并且多个defer会按照顺序执行,而async则是在js加载好之后就会执行,并且多个async,哪个加载好就执行哪个
生成器Generators/ yield
Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同,Generator 最大的特点就是可以控制函数的执行。
语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
Generator 函数除了状态机,还是一个遍历器对象生成函数。
可暂停函数, yield可暂停,next方法可启动,每次返回的是yield后的表达式结果。
yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。
- js
function *foo(x) { let y = 2 * (yield (x + 1)) let z = yield (y / 3) return (x + y + z) } let it = foo(5) console.log(it.next()) // => {value: 6, done: false} console.log(it.next(12)) // => {value: 8, done: false} console.log(it.next(13)) // => {value: 42, done: true}
- 首先 Generator 函数调用和普通函数不同,它会返回一个迭代器
- 当执行第一次 next 时,传参会被忽略,并且函数暂停在 yield (x + 1) 处,所以返回 5 + 1 = 6
- 当执行第二次 next 时,传入的参数12就会被当作上一个yield表达式的返回值,如果你不传参,yield 永远返回 undefined。此时 let y = 2 * 12,所以第二个 yield 等于 2 * 12 / 3 = 8
- 当执行第三次 next 时,传入的参数13就会被当作上一个yield表达式的返回值,所以 z = 13, x = 5, y = 24,相加等于 42
event-loop(事件循环/轮询)
js是单线程运行的,异步(setTimeout, ajax)要基于回调实现,event loop就是**异步回调的实现原理,**DOM事件也使用回调,基于event loop
js的执行
- 从前到后一行一行执行
- 如果某一行报错,则停止下面代码的执行
- 先执行同步,再执行异步
event loop过程1
- 同步代码,一行一行放到call stack执行
- 遇到异步,先记录下,等待时机(定时、网络请求等),时机到了,就移动到callback quene
event loop过程2
- 如果call stack为空(即同步代码执行完)event loop开始工作
- 轮询查找callback quene,如有任务则移动到call stack中执行
- 然后反复轮 询查找
async/await
async/await 语法上,以同步的方式写的代码能够异步执行,是消灭异步回调的终极武器,但和Promise并不排斥,两者相辅相成
async/await和Promise的关系
- 执行async函数,返回的是Prmoise对象
- await相当于Promise的then
- try...catch可捕获异常,代替了Prmoise的catch
执行顺序
- js
async function async1(){ console.log('async1 start');// 2 await async2() //undefined // await 后面的内容都可以看作callback里的内容,即异步 console.log('async1 center'); // 5 await async3() console.log('async1 end'); // 7 } async function async2(){ console.log('async2');// 3 } async function async3(){ console.log('async3'); // 6 } console.log('script start');// 1 async1() console.log('script end');// 4
for ... of
for ... in(以及forEach for) 是常规的同步遍历
for ... of 常用于异步的遍历
- js
function muti(num) { return new Promise((reslove, reject) => { setTimeout(() => { reslove(num * num); }, 1000); }); } const nums = [1, 2, 3] // 一秒后同时打印1 4 9 nums.forEach(async (item) => { const res = await muti(item) console.log(res); }); // 每次打印间隔一秒 !(async function(){ for(let i of nums){ const res = await muti(i) console.log(res); } })()
微任务/宏任务
宏任务:setTimeout, setInterval, Ajax, DOM事件
微任务:Promise async/await
微任务执行的时机比宏任务早
event loop和DOM渲染
- 每次call stack清空(每次轮询结束),同步代码执行完毕
- 执行当前的微任务,从micro task queue取出
- 尝试DOM的渲染
- 然后再去触发下一次event loop
微任务/宏任务的区别
宏任务:DOM渲染后触发,如seTimeout,是由浏览器规定的
微任务:DOM渲染前触发,如Promise,是由ES6语法规定的
- js
const box = document.getElementById('box') const p = document.createElement('p') p.innerText = 'this is p' box.appendChild(p) // 微任务:DOM渲染前触发 Promise.resolve().then(()=>{ console.log(p.innerText); alert('Promise') //此时DOM未渲染 }) // 宏任务:DOM渲染后触发 setTimeout(()=>{ console.log(p.innerText); alert('setTimeout') //此时DOM渲染完成 })
从event loop解释,为何微任务执行更早
- 当执行微任务时,会等待时机,时机到后放入微任务队列
- 而event loop会在call stack清空后先将所有的微任务一个一个取出来执行
- 再尝试DOM的渲染,最后取出callback quene中的任务执行