js是一个单线程的脚本语言,之所以是单线程是由于js要操作dom,如果在同一时间,一个线程修改dom,一个线程删除dom,这个时候浏览器会不知该如何处理,为了避免这种情况的出现,所以js只能按照顺序从上到下执行,这种从上到下按顺序执行就是同步任务。
一、同步任务和异步任务
js里的“同步”指的是代码从上到下按顺序执行,这种执行方式有一个问题,当遇到计算量比较大的任务时,代码会等待计算结束再继续执行,这个时候界面会卡死不动,为了解决这个问题,就有了“异步”这个概念;“异步任务”指的就是当遇到计算量比较大的函数时,给这个函数单独开一个线程去运行这个计算费时的任务,代码继续执行,当计算完成后,再执行计算后回调函数内的代码。
js引擎通过事件循环的机制来处理什么时候执行同步代码,什么时候执行异步代码
二、事件循环、宏任务、微任务
js在执行的时候,可以理解为是一个函数调用栈,当执行到某个函数时,将当前这个函数压入调用栈,当执行结束后,弹出调用栈;如果遇到异步任务时,调用栈不会等待函数执行完毕,而是开启这个异步任务后,将该函数弹出调用栈;当异步任务执行成功后,会放入一个单独的事件队列里,当同步函数都执行完成后,事件循环机制会在这个事件队列里查找是否有需要执行的函数,如果有,就会依次压入函数调用栈;
function fn1 () {
console.log('fn1')
}
function fn2 () {
setTimeout(()=>{
console.log('fn2')
}, 2000)
}
console.log('fn----')
fn2()
fn1()
// fn----
// fn1
// fn2
上面代码中,调用栈依次是:
1、console.log('fn----'); 入栈 出栈
2、fn2入栈,setTimeout入栈,监测到是一个异步任务,单独开辟一个线程计时,2s后放入事件队列里;setTimeout出栈,fn2出栈
3、fn1入栈, console.log('fn1')入栈, console.log('fn1')出栈,fn1出栈
4、所有同步代码执行完成后,事件循环检测事件队列里是否有需要执行的函数,当2s后发现了console.log('fn2')需要执行,将其入栈,出栈
5、至此,所有代码执行完成
如果有很多异步任务,这些异步任务都会在倒计时结束后进入到事件队列里排队,这些在事件队列里排队的任务就是宏任务,还有一种异步任务不会在这个事件队列里排队,只要是当前任务执行结束,而新的任务还没开始,这个时候会优先执行这个异步任务,这个就是微任务,promise就是其中之一
function fn1 () {
console.log('fn1')
}
setTimeout(()=>{
console.log('setTimeout---')
},0)
Promise.resolve().then(()=>{
console.log('promise---')
})
fn1()
// fn1
// promise---
// setTimeout---
上面代码中,Promise的执行要先于setTimeout
三、回调函数
在处理异步任务时,传入一个回调函数是之前比较常用的写法
function fn (callBack) {
setTimeout(()=>{
callBack('done')
}, 200)
}
fn((data)=>{
console.log(data)
})
这种写法的缺点是处理复杂的异步任务时,容易产生回调嵌套的问题,嵌套过深不利于阅读,也不利于维护,ES6中新增的Promise可以解决这个问题。
四、Promise
关于Promise的学习笔记已经整理在另一篇文章中,这里不做重复
理解promise
五、Generator
使用generator实现"理解promise"文章中的例子
let data = {
story: ['title1', 'title2', 'title3', 'title4', 'title5', 'title6'],
title1: '第一段段落,第一段段落第一段段落第一段段落第一段段落第一段段落第一段段落第一段段落第一段段落',
title2: '第二段段落,第二段段落第二段段落第二段段落第二段段落第二段段落第二段段落第二段段落第二段段落',
title3: '第三段段落,第三段段落第三段段落第三段段落第三段段落第三段段落第三段段落第三段段落第三段段落',
title4: '第四段段落,第四段段落第四段段落第四段段落第四段段落第四段段落第四段段落第四段段落第四段段落',
title5: '第五段段落,第五段段落第五段段落第五段段落第五段段落第五段段落第五段段落第五段段落第五段段落',
title6: '第六段段落,第六段段落第六段段落第六段段落第六段段落第六段段落第六段段落第六段段落第六段段落'
}
let time = {
story: 1000,
title1: 3000,
title2: 2000,
title3: 1000,
title4: 4000,
title5: 5000,
title6: 6000,
}
var getData = function (key) {
return new Promise((resolve, reject) => {
setTimeout(()=>{
resolve(data[key])
}, time[key])
})
}
// 定义生成器函数
function * main () {
const titleLists = yield getData('story')
for(let item of titleLists) {
yield getData(item)
}
}
const g = main()
function handle (result) {
if (result.done) {
return;
}
result.value.then((tittle)=>{
let _result = g.next(tittle)
// 递归调用
handle(_result)
})
}
// 函数开始执行
handle(g.next())
上面代码中都是按顺序执行,即获取完第一段落再请求第二段落,优化后的代码如下:
function * main () {
const titleListsPromise = yield getData('story')
for(let itemPromise of titleListsPromise) {
yield itemPromise
}
}
function handle (result) {
if (result.done) {
return
}
result.value.then((content)=>{
console.log(content)
result = g.next()
handle(result)
})
}
const g = main()
let re = g.next()
re.value.then((titleLists)=>{
let _promiseLists = titleLists.map(getData)
handle(g.next(_promiseLists))
})
六、Async/await
Async/await其实是generator的语法糖,通过一个例子看一下generator的一般用法
let data = {
story: ['title1', 'title2', 'title3', 'title4', 'title5', 'title6'],
title1: '第一段段落,第一段段落第一段段落第一段段落第一段段落第一段段落第一段段落第一段段落第一段段落',
title2: '第二段段落,第二段段落第二段段落第二段段落第二段段落第二段段落第二段段落第二段段落第二段段落',
title3: '第三段段落,第三段段落第三段段落第三段段落第三段段落第三段段落第三段段落第三段段落第三段段落',
title4: '第四段段落,第四段段落第四段段落第四段段落第四段段落第四段段落第四段段落第四段段落第四段段落',
title5: '第五段段落,第五段段落第五段段落第五段段落第五段段落第五段段落第五段段落第五段段落第五段段落',
title6: '第六段段落,第六段段落第六段段落第六段段落第六段段落第六段段落第六段段落第六段段落第六段段落'
}
let time = {
story: 1000,
title1: 3000,
title2: 2000,
title3: 1000,
title4: 4000,
title5: 5000,
title6: 6000,
}
var getData = function (key) {
return new Promise((resolve, reject) => {
setTimeout(()=>{
resolve(data[key])
}, time[key])
})
}
function co (generator) {
function handle (result) {
if (result.done) {
return
}
result.value.then((title)=>{
result = generator.next(title)
handle(result)
})
}
handle(generator.next())
}
function * main () {
const title1 = yield getData('title1')
console.log(title1)
const title2 = yield getData('title2')
console.log(title2)
const title3 = yield getData('title3')
console.log(title3)
const title4 = yield getData('title4')
console.log(title4)
const title5 = yield getData('title5')
console.log(title5)
const title6 = yield getData('title6')
console.log(title6)
}
co(main())
上面代码中封装了一个co函数,用来专门处理生成器函数,直接使用async/await可以代替co函数
async function main () {
const title1 = await getData('title1')
console.log(title1)
const title2 = await getData('title2')
console.log(title2)
const title3 = await getData('title3')
console.log(title3)
const title4 = await getData('title4')
console.log(title4)
const title5 = await getData('title5')
console.log(title5)
const title6 = await getData('title6')
console.log(title6)
}
const result = main()
result.then(data=>console.log('compolete'))
这里我们直接调用main函数,无需借助co函数就可以实现上述逻辑,并且async会返回一个promise对象,方便做统一处理。