异步函数
1. 什么是线程
一个线程就是一个基本的处理过程,程序用它来完成任务。每个线程一次只能执行一个任务,其他任务都要在后面排队等待;
现在的计算机大多都有多个内核,因此可以同时执行多个任务。支持多线程的编程语言可以使用功能计算机的多个内核,同时完成多个任务;
进程是资源分配的最小单位,线程是CPU调度的最小单位
浏览器每个页面下有5个线程:
- 渲染线程(画页面);
- js引擎线程(执行脚本代码);
- 遇见setTimeout promise之类的异步, 新起一个线程去处理其中的异步任务;
- 遇见事件处理函数, 新起一个一个线程去执行事件处理函数中的代码;
- 遇见ajax新起一个线程专门去做请求,请求完成把结果丢到事件队列中;
2. JavaScript是单线程
所谓的单线程是指:JS引擎中负责解释和执行JavaScript代码的线程只有一个,也就是一次只能完成一项任务,这个任务执行完毕后才能执行下一个,它会[阻塞]其他任务。
单线程模式:
优点: 实现起来比较简单,执行环境相对单纯;
缺点: 只要有一个任务耗时很长,后面的任务都必须排队跟着,会拖延整个程序的执行;
JavaScript运行时,除了一个正在运行的主线程,JS引擎还提供一个任务队列,里面是各种需要当前程序处理的异步任务。(实际上,根据异步任务的类型,存在多个任务队列)
3.什么是异步?
异步模式可以一起执行多个任务;
简单来说就是: 当你向服务器请求数据, 等待服务器返回数据需要30s, 这时有两种情况:
1. 等待服务器返回数据,这期间不执行后续代码,等服务器返回数据后, 再执行后面的代码;
2. 充分利用这等待的30s,跳过这一步去执行其他的代码,等服务器返回数据了,在回来执行返回数据后的代码;
这第一种情况称为"同步", 而第二种情况就是"异步";
同步任务和异步任务
代码执行: 先执行同步, 再执行异步。
同步任务: 那些没有被引擎挂起、在主线程排队执行的任务。只有前一个任务执行完毕,才能执行后一个任务。
异步任务: 那些被引擎放在一边、不进入主线程,而进入任务队列的任务。只有引擎认为某个异步任务可以执行了(比如Ajax从服务器得到了数据),该任务(采用回调函数的形式)才会进入主线程执行。排在异步任务后面的代码,不用等待异步任务结束, 而是会马上执行,也就是说异步任务不具有"堵塞"效应。
那么JavaScript引擎又怎么知道异步任务有没有结果, 能不能进入主线程呢?答案就是: 引擎在不停的检查,一遍又一遍,只要同步任务执行完毕了,引擎就会去检查那些被挂起来的异步任务,是不是可以进入主线程了。这种循环检查的机制叫做事件循环。
4. 哪些情况属于异步?
JS中常见的异步调用:
定时器;
ajax;
事件函数;
- 回调函数
function eat () {
console.log('好的,我开动咯');
}
function cooking(cb) {
console.log('妈妈认真做饭');
setTimeout(function () {
console.log('小明快过来,开饭啦')
cb();
},1000);
}
function read () {
console.log('小明假装正在读书');
}
cooking(eat);
read();
/*
执行顺序:
妈妈认真做饭;
小明假装正在读书;
小明快过来,开饭啦;
好的,我开动咯;
*/
- 事件监听
function eat() {
console.log('妈妈敲门啦,该去吃饭啦');
}
function cooking () {
console.log('妈妈认真做饭');
setTimeout(function () {
console.log('小明,出来吃饭啦')
bus.$emit('done');
},3000);
}
function read() {
console.log('小明又假装在读书');
bus.$son('done', eat);
}
cooking();
read();
/*
执行顺序:
妈妈认真做饭;
小明又在假装读书;
小明,出来吃饭啦;
妈妈敲门啦,该去吃饭啦;
*/
- 订阅与发布
function eat () {
console.log('爸爸叫我去吃饭啦');
}
function cooking () {
console.log('妈妈认真做饭');
setTimeout(function () {
console.log('孩子他爸,叫小明出来吃饭');
}, 3000);
}
function read () {
console.log('小明依旧假装正在读书');
Dad.subscribe('done', eat);
}
cooking();
read();
/*
执行顺序:
妈妈认真做饭;
小明依旧正在假装读书;
孩子他爸,叫小明出来吃饭;
爸爸叫我去吃饭啦;
*/
- promise
案例一:
function read () {
console.log('小明认真读书');
}
function eat () {
return new Promise((resolve,reject) => {
console.log('好嘞,吃饭咯');
setTimeout(() => {
resolve('饭吃饱啦');
}, 1000)
})
}
function wash () {
return new Promise((resolve, reject) => {
console.log('唉, 又要洗碗');
setTimeout(() => {
resolve('碗洗完啦');
}, 1000)
})
}
function mop () {
return new Promise((resolve, reject) => {
console.log('唉, 又要拖地');
setTimeout(() => {
resolve('地拖完啦');
}, 1000)
})
}
const cooking = new Promise((resolve, reject) => {
/* 这一步属于同步任务, 所以接下来执行另外一个同步任务: read(); */
console.log('妈妈认真做饭');
/* 同步任务执行完毕后, 开始执行异步任务; */
setTimeout(() => {
resolve('小明快过来,开饭啦');
}, 3000)
})
/* 前一个then执行完毕后, 才能执行下一个then */
cooking.then(msg => {
console.log(msg);
return eat();
}).then(msg => {
console.log(msg);
return wash();
}).then(msg => {
console.log(msg);
return mop();
}).then(msg => {
console.log(msg);
console.log('终于结束啦, 出去玩咯');
})
read();
/*
执行顺序:
妈妈认真做饭;
小明认真读书;
小明快过来, 开饭啦;
好嘞,吃饭咯;
饭吃饱啦;
唉,又要洗碗;
碗洗完啦;
唉, 又要拖地;
地拖完啦;
终于结束啦, 出去玩咯;
*/
案例二:
console.log(1)
let promise = new Promise((resolve, reject) => {
//内部执行异步操作
console.log(2) // 这一步是同步任务
resolve(123)
//setTimeout(() => {}, 1000)
})
promise.then(data => {
console.log(4)
console.log('data', data) // 这里的data代表成功时执行的函数(resolve)中的结果数据;
})
// setTimeout第二个参数定义延迟的时间,如果定义的时间小于4ms,浏览器会默认按照4ms执行;
// setTimeout第三个参数,都会被传递给第一个函数参数;
setTimeout(
arg => {
console.log(5)
console.log(args)
},
0,
{a: 1, b: 2}
)
console.log(6)
/*
console.log 打印结果顺序:
1
2
6
4
123
5
*/
- 定时器
- ajax
- setInterval
宏任务: setTimeout setInterval, 异步任务中的微任务先被执行,宏任务后执行;
微任务: promise mut
注: 里面有借鉴前辈的成果