JS-同步、异步?区别?回调函数?

982 阅读4分钟

「这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战」(juejin.cn/post/702364… "juejin.cn/post/702364…")

同步

同步任务就是排队在主线程上执行的任务,只有前一个任务执行完毕,才会执行下一个任务。

console.log('first')
console.log('second')

这里打印的结果是first-second,也就是说同在主线程的任务,会从上到下依次执行,前面代码会阻塞后面的代码执行。

异步

异步任务就是不进入主线程,而是进入到任务队列里面的任务。当主线程所有任务执行完毕之后,任务队列会通知主线程请求执行任务,该任务才会进入主线程执行。

console.log('first')
setTimeout(
   () => { console.log('second');
}, 2000);
console.log('third')

这里最后输出的结果是first-third-second,我们发现定时器里面的任务是最后一个执行的。因为定时器里面的任务被放在了任务队列里面。这里就设计了JS的代码执行规则。 在这里插入图片描述 执行的过程是:

  1. 先执行执行栈中的同步任务。
  2. 当遇到异步任务时,将其放在异步任务处理区中,之后继续执行下面的同步任务。
  3. 当异步任务在异步处理区中被触发,JS会按照触发的先后顺序将其放在任务队列里面等待被调用。
  4. 等到执行栈中的同步任务执行完毕,系统就会按照次序读取任务队列中的异步任务,于是被读取的异步任务结束等待状态,进入到执行栈开始执行。

同步和异步的区别

我们都知道同步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); // 失败了)