# 同步, 异步与回调

# 同步与异步的区别

同步与异步是在处理 I/O 操作, 网络请求时的解决方案, 比如读取一个文件时, 程序不会往下执行而是等待文件读取的结果, 这视为同步. 又比如在发送一个 HTTP 请求后, 程序继续往下执行代码, 待 HTTP 请求返回结果后才处理返回结果, 这视为异步.

比较同步与异步, 会发现异步是更好的. 在前端有一个例子, 一个 button 点击后会调用 Ajax 发起请求, 如果使用同步, 那么在发起请求到请求返回结果这一段时间内, 整个页面是无法响应的, 因为 JS 引擎会等待 HTTP 请求结果, 不会去执行其他的代码. 异步则不会出现这样的问题, 所以异步也是广为使用的方案. 如果非要我生搬硬套同步的优点, 那我只能说: 嗯, 足够无脑, 不懂一点编程的小白也会用.

# 异步解决方案

# 轮询

当请求发送出去后, 我们会每间隔一段时间去查询结果是否返回. 这就是轮询.

// 向某站点发送请求
function getResponse() {
  let response = ajax("GET", "/xxx");
  setInterval(() => {
    if (response) {
      console.log("我请求到结果了:", response);
    } else {
      console.log("别催我!我在找呢!");
    }
  }, 1000);
}
1
2
3
4
5
6
7
8
9
10
11

# 回调

根据维基百科对回调的定义:

a callback, also known as a "call-after" function, is any executable code that is passed as an argument to other code that is expected to call back (execute) the argument at a given time. This execution may be immediate as in a synchronous callback, or it might happen at a later time as in an asynchronous callback.

回调其实是一个可执行的函数, 通常作为参数传入另一个函数被执行. 执行的时机可能是立即执行, 也可能是过一段时间执行.

function fn(f1) {
  f1();
}

function f2() {
  // doSomething...
}

fn(f2);
1
2
3
4
5
6
7
8
9

按照定义, f2就是一个回调.

再看一例:

let arr = [1, 2, 3];
arr.forEach(n => {
  console.log(n);
});
1
2
3
4

n => { console.log(n) }也是一个回调.

很容易发现, 在异步解决方案中, 回调的做法比轮询好多了, 在 I/O 操作, 网络请求发出后只需在结果返回时调用回调即可.

ajax("GET", "/xxx", (error, data) => {
  // handle response data and error...
});
1
2
3

回调也有自身的局限, 当嵌套的回调过多会出现回调地狱: 回调地狱

它使得我们梳理代码逻辑时会有很多困扰, 于是后来前端出现了Promise/A+规范, 制定了异步请求的一系列规范. 随后 ES6 推出了Promise. 以后我会再写一篇关于 Promise 的博文, 以及它的实现原理并手写出来.