浅析异步与 Promise(一)

215 阅读4分钟

什么是异步?什么是同步?

如果能直接拿到结果,那就是同步,比如在医院挂号,拿到号才离开窗口。同步任务可能消耗10毫秒,也可能需要3秒。

如果不能直接拿到结果,那就是异步,比如在餐厅门口等位,拿到号可以先去逛街,那什么时候才能真正吃饭呢?可以每10分钟去餐厅问一下(轮询),也可以扫码用微信接收通知(回调)。

异步举例

var request = new XMLHttpRequest()
request.open('GET', '/a/b/c?name=ff', true);
request.onreadystatechange = function() {
  if (request.readyState === 4 && request.status === 200) {
    console.log(request.responseText);
  }
};
request.send();

以 AJAX 为例,request.send() 之后,并不能直接得到 response,必须等到 readyState 变为4后,浏览器回头调用 request.onreadystatechange 函数,我们才能得到 request.response,这跟餐厅发送微信提醒的过程是类似的。

什么是回调

  • 写给自己用的函数,不是回调
  • 写给别人用的函数,就是回调

写了却不调用,给别人调用的函数,就是回调。request.onreadystatechange 就是写给浏览器调用的,意思就是浏览器回头调用一下这个函数。

回调举例

function f1() {}
function f2(fn) {
  fn()
}
f2(f1)

分析:f1 自身没有调用而是传给了 f2(别人),f2 调用了 f1,也就是说 f1 是写给 f2 调用的函数,所以,函数 f1 是回调。

异步和回调的关系

异步和回调的关联:

异步任务需要在得到结果时通知 JS 来拿结果,怎么通知呢?

可以让 JS 留一个函数地址(电话号码)给浏览器,异步任务完成时浏览器调用该函数地址即可(拨打电话),同时把结果作为参数传给该函数(电话里说可以来吃了),这个函数就是写给浏览器调用的,所以是回调函数。

异步和回调的区别:

  • 异步任务需要用到回调函数来通知结果(不一定非要用回调,还可以用轮询)
  • 但回调函数不一定只用在异步任务里,回调可以用到同步任务里,比如 array.forEach(n => console.log(n)) 就是同步回调

判断同步和异步(根据特征或文档)

如果一个函数的返回值处于 setTimeoutAJAX(即 XMLHttpRequest)、AddEventListener 这三个东西内部(不限于这三种),那么这个函数就是异步函数。

request.open(method, url, false) 可以把 AJAX 设为同步的,但是一般不要把 AJAX 设置为同步的,这样做会使请求期间页面卡住。

异步函数的例子——摇骰子

function 摇骰子() {
  setTimeout(() => {  //箭头函数
    return parseInt(Math.random() * 6) + 1
  }, 1000)
  //return undefined
}
const n = 摇骰子()
console.log(n)  //undefined

摇骰子() 没有写 return,那就是 return undefined,箭头函数里有 return,返回真正的结果,所以这是一个异步函数/异步任务。

那怎么拿到异步结果?

可以用回调。写个函数,然后把函数地址给他。

function f1(x) {console.log(x)}
摇骰子(f1)

然后要求摇骰子函数得到结果后把结果作为参数传给 f1.

function 摇骰子(fn) {
  setTimeout(() => {
    fn(parseInt(Math.random() * 6) + 1)
  }, 1000)
}

由于 f1 声明后只用了一次,所以可以删掉 f1,并简化为箭头函数。

function f1(x) {console.log(x)}
摇骰子(f1)
//改为
摇骰子(x => console.log(x))
//再简化为
摇骰子(console.log)  //如果参数个数不一致就不能这样简化

摇骰子例子的完整代码:

function 摇骰子(fn) {
  setTimeout(() => {
    fn(parseInt(Math.random() * 6) + 1)
  }, 1000)
}
摇骰子(x => console.log(x))  //打印出 1-6 的随机数

总结

  • 异步任务不能拿到结果
  • 于是我们传一个回调给异步任务
  • 异步任务完成时调用回调
  • 调用的时候把结果作为参数

ps 面试题:下面代码打印出的结果是什么?

const array = ['1', '2', '3'].map(parseInt)
console.log(array)

答:[1, NaN, NaN](箭头函数的错误简化问题)

上面代码简化前的写法是这样的:

const array = ['1', '2', '3'].map((item, i, arr) => {
    return parseInt(item, i, arr)  //parseInt只接收两个参数,所以 arr 被忽略
}
//parseInt('1', 0, arr) => 1(0为无效参数,所以结果为1)
//parseInt('2', 1, arr) => NaN(把'2'作为一进制数进行解析,一进制只有0,所以结果为NaN)
//parseInt('3', 2, arr) => NaN(把'3'作为二进制数进行解析,二进制只有0和1,所以结果为NaN)
console.log(array)  //[1, NaN, NaN]

正确写法:

const array = ['1', '2', '3'].map(item => parseInt(item))
console.log(array) //[1, 2, 3]