写在前面

同步 / 异步 / 回调 是 JavaScript 中的几个重要概念,也不仅仅是在 JS 语言中,在所有的编程语言中,这几个概念都是很重要的,这里做一个详细介绍。

1. 同步与异步

同步和异步是一对相反的概念,同步的意思简单来说是指能直接拿到结果的操作就是同步操作,相反地,异步的意思是指不能直接拿到结果的操作就是异步操作,举例如下:

你拿着一个文件找领导签字,领导看完文件立马签字,或者临时接个电话喝口水,你在那里等着,然后领导做完他的事后就立马签字,你在送去文件签字的时候能立马拿到结果,无论是立马还是等不太长的时间后拿到签字结果的过程,就是同步操作

若还是上一个场景,领导看完文件后觉得文件有些问题或者需要再向上级领导审核一下,于是领导告诉你说这个文件得再审核一下才能签字,此刻你不能立马拿到结果,需要等待一个不确定的时间或者很长的时间才能拿到签字结果,于是领导告诉你先去忙其他的事,等他向上级审核完签字后再告诉你,你再去拿签字结果。这个拿到结果的过程就是异步操作

因此总结同步与异步操作如下:

同步:能直接拿到结果的操作就是同步操作

异步:不能直接拿到结果的操作就是异步操作

无论是同步还是异步操作,都是会执行某一操作,拿到操作结果的操作,无论同步还是异步,都需要拿到结果,只不过二者拿到结果的时间不同。

2. 回调

回调 callback 是一种特殊的函数,该函数特殊在其在被声明定义后,不是由声明定义该函数的人调用,而是传递给别人,让别人在需要的时候调用。

回调常常用于这样一个场景,fn 函数作为形式参数传递给另外一个函数 fn2,在函数 fn2 中调用该形参,也就是调用 fn 函数,那么 fn 函数就是回调,如下:

  1. function fn(){
  2. console.log('我是回调函数fn')
  3. }
  4. function fn2(f){
  5. f()
  6. }
  7. fn2(fn) // 输出 “我是回调函数fn”

因此简单来说就是:我声明定义了一个函数,但不是我调用它,而是传递给别人让别人调用,这函数就是回调。

3. 异步与回调

异步常常用到回调拿到结果,但不一定非要用回调才能拿到结果
回调常常用于异步操作,但回调也可用于同步操作

因此异步和回调是一个相互合作的关系。

由上述的异步定义可知,异步虽然不能立马拿到结果,但是最终还是必须要拿到操作结果才肯罢休的。那么如何拿到异步处理的操作呢?

拿到异步操作的方法有两种,分别是 轮询回调

轮询就是指每隔一段时间去询问异步操作是否处理完了,若没处理完就回去再等一会,等一会再去询问处理完了没,直到处理完了拿到处理结果才停止询问,这样拿到异步操作结果的方式就是轮询。

回调就是指和执行异步操作的人商量好,在交给其异步操作事件的同时留下一个回调函数,让他在执行完异步操作后将操作结果作为参数传递给留下来的回调函数,也就是调用一下留下来的回调函数。这样拿到异步操作结果的方式就是回调。

无论是通过哪种方式拿到异步操作结果,都是需要对异步操作结果进行某些处理的,或者根据操作结果来判断是否继续往下执行的,因此在拿到结果后对结果进行处理时 轮询 和 回调 出现差异。一般情况下,轮询 是要在拿到异步操作结果后才根据结果执行某些操作,而 回调 是自己已经知道异步操作处理完成后会将结果作为参数传递给它,因此回调便可以在操作结果出来前将形式参数作为处理结果写好处理结果的代码,然后等待异步操作结果出来回调函数被调用即可。

4. 同步与回调

上述提到,回调不仅仅可以用于异步操作,也可以用于同步操作,上面的函数执行就是同步操作的回调,其中 fn2 是同步函数,fn 是回调函数,在 fn2 的执行中用到了回调函数 fn

  1. function fn(){
  2. console.log('我是回调函数fn')
  3. }
  4. function fn2(f){
  5. f()
  6. }
  7. fn2(fn) // 输出 “我是回调函数fn”

5. 哪些函数是异步的

仅仅说了异步和同步的区别,但到底哪些函数是异步函数呢?

如果一个函数的返回值处于以下异步API内部,那么这个函数就是异步函数

setTimeout
AJAX(即XMLHTTPRequest)
AddEventListener

6. 一个异步操作结合回调的示例

  1. function getDice(){
  2. setTimeout(()=>{
  3. return parseInt(Math.random()*6) + 1
  4. },1000)
  5. }
  6. let n = getDice() //getDice是个异步函数,其返回值不能立马拿到
  7. console.log(n) //undefined

那么该如何拿到异步操作的结果呢?使用回调

  1. function ff(x){
  2. console.log(x);
  3. }
  4. function getDice(){
  5. setTimeout(()=>{
  6. ff(parseInt(Math.random()*6) + 1) //结果不直接返回,而是通过回调传出去
  7. },1000)
  8. }
  9. getDice() //将后续的输出操作结果的语句放到了回调函数里执行

异步任务不能拿到结果

传递一个回调给异步函数

异步任务完成时调用回调

调用的时候把结果作为参数