同步
同步任务就是排队在主线程上执行的任务,只有前一个任务执行完毕,才会执行下一个任务。
console.log('first')
console.log('second')
这里打印的结果是first-second,也就是说同在主线程的任务,会从上到下依次执行,前面代码会阻塞后面的代码执行。
异步
异步任务就是不进入主线程,而是进入到
任务队列里面
的任务。当主线程所有任务执行完毕之后,任务队列
会通知主线程请求执行任务,该任务才会进入主线程执行。
console.log('first')
setTimeout(
() => { console.log('second');
}, 2000);
console.log('third')
这里最后输出的结果是first-third-second,我们发现定时器里面的任务是最后一个执行的。因为定时器里面的任务被放在了任务队列
里面。这里就设计了JS的代码执行规则。
执行的过程是:
- 先执行执行栈中的同步任务。
- 当遇到异步任务时,将其放在异步任务处理区中,之后继续执行下面的同步任务。
- 当异步任务在异步处理区中被触发,JS会按照触发的先后顺序将其放在
任务队列
里面等待被调用。 - 等到执行栈中的同步任务执行完毕,系统就会按照次序读取
任务队列
中的异步任务,于是被读取的异步任务结束等待状态,进入到执行栈开始执行。
同步和异步的区别
我们都知道同步API可以从返回值中获取到执行结果,但是异步API却不能。
//同步
function sum(a, b){
return a + b;
}
const result = sum(1, 2)
console.log(result) //3
异步
function getMsg(){
setTimeout(function(){
return {msg:'Hi'}
}, 2000)
}
const msg = getMsg()
console.log(msg) //undefined
为什么是undefined呢?
原因就是当代码执行到定时器的时候,会将异步代码放到
任务队列
中,之后继续执行同步代码。
在这里定时器被放到任务队列里面,之后继续执行代码也就是调用了getMsg()函数,这个时候getMs()函数里面的异步函数还没有被触发,异步函数并没有阻塞线程,代码继续执行,getMsg()函数默认返回了一个undefined。
那么异步函数怎么获取异步API返回值呢?需要一个新的概念——回调函数
回调函数获取异步API返回值
通过在函数形参中拿到的回调函数引用地址,可以调用回调函数并传递参数给回调函数的形参,这样回调函数就可以获得被调用函数中的信息。
举个例子
function fn(callback){
callback('Hi')
}
fn(function(n){
console.log(n)
})
//输出结果是Hi
由此推断,刚刚的代码也可以写成:
function getMsg(callback){
setTimeout(function(){
callback({
msg:'Hi'
})
},2000)
}
getMsg(function(data){
console.log(data)
})
//输出结果是Hi
回调函数解决了异步API不能获取返回值的问题。
接着我们看一个小案例
console.log('代码开始执行')
setTimeout(() => {
console.log('2s后执行的结果')
}, 2000)
setTimeout(() => {
console.log('0s后执行的结果')
}, 0)
console.log('代码执行结束')
由于JS的代码执行机制,输出的结果是:代码开始执行--代码执行结束--0s后执行的结果--2s后执行的结果
。
这显然不是我们需要得到的执行顺序,那么如何解决呢?使用回调函数
console.log('代码开始执行')
function getZore(callback) {
setTimeout(function () {
callback('123')
}, 0)
}
getZore(function (data) {
console.log('回调函数被执行')
console.log(data)
console.log('回调函数执行结束')
})
console.log('代码执行结束')
这里的执行结果是代码开始执行--代码执行结束--回调函数被执行--123--回调函数执行结束
。
我们发现在回调函数中代码是依次进行的。
回调函数的作用就是,将异步代码写在一个块级作用域中,当其中的异步函数执行完毕后,才调用这个回调函数,这时异步函数已经执行完毕,所以在回调函数中代码就可以如同同步代码一样依次执行。
console.log('代码开始执行')
function getZore(callback) {
setTimeout(function () {
console.log('2s执行结果')
setTimeout(function () {
console.log('0s执行结果')
callback('123')
}, 0)
}, 2000)
}
getZore(function (data) {
console.log(data)
})
console.log('代码执行结束')
直接结果是:代码开始执行--代码执行结束--2s执行结果--0s执行结果--123
这样做会有一个后果,就是回调地狱
。
为了解决回调地狱,ES6里面提供了Promise
Promise
Promise实际上就是在原本的异步API上面包裹一层函数,其中Promise函数的resolve , reject两个参数,实际上和普通的回调函数一样,都接受一个回调函数作为实参,而在运行时返回一个实参给调用他的then或catch两个回调函数,这样就会获得异步API中的执行结果。
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
if (true) {
resolve({name: '张三'})
}else {
reject('失败了')
}
}, 2000);
});
promise.then(result => console.log(result); // {name: '张三'})
.catch(error => console.log(error); // 失败了)