异步与Promise
异步
如果能直接拿到将结果
那就是同步
比如你在医院挂号,你拿到号才会离开窗口
同步任务可能消耗 10 毫秒,也可能需要 3 秒
总之不拿到结果你是不会离开的,这就是同步
如果不能直接拿到结果
那就异步
比如你在餐厅门口等位,你拿到号后可以去干别的事比如逛街
你可以每 10 分钟取餐厅问一下排到自己了没(轮询)
也可以扫码用微信接受通知(回调)
异步举例
以 AJAX 为例
request.send()
之后,并不能直接得到 response用
console.log(request.response)
试试必须等到 readyState 变为 4 后,浏览器才会回头调用
request.onreadystatechange
函数我们才能得到
request.response
这就跟餐厅给你发微信提醒的过程类似
回调 callback
你写给自己用的函数,不是回调
你写给别人用的函数,就是回调
request.onreadystatechange
就是写个浏览器调用的意思是你(浏览器)回头调用一下这个函数
简单来理解就是:同步按你的代码顺序执行,异步不按照代码顺序执行,异步的执行效率更高。
1 | getJSON.onclick = () => { |
回调
写了却不调用,给别人调用的函数,就是回调。需自行意会
函数例子:
1 | function f1() {} |
分析:
- 我调用 f1 没有?
- 我把 f1 传给 f2 (别人)了没有?
- f2 调用 f1 了没有?
答:1. 调用了。2.穿了。3.f2 调用了 f1。
那么,f1 是不是我写给 f2 调用的函数? 是。
所以,f1 是回调。
例子2:
1 | function f1(x) { |
fn(‘hello’) 中的 fn 就是 f1 。
fn(’hello‘) 中的 ’hello’ 会被赋值给参数 x 。
所以 x 就是 ‘hello’。
异步和回调的关系
关联
异步任务需要再得到结果时通知 JS 来拿结果
怎么通知?
可以让 JS 留一个函数地址给浏览器(电话号码)
异步任务完成时浏览器调用该函数即可(拨打电话)
同时把任务作为参数传给该函数(通知)
这个函数是我写给浏览器调用的,所以是回调函数
区别
异步任务需要用到回调函数来通知结果
但回调函数不一定只用在异步任务里
回调可以用到同步任务里
array.forEach(n=>console.log(n))
就是同步回调
判断同步异步
如果一个函数的返回值处于
- setTimeout
- AJAX(即 XMLHttpRequest)
- AddEventListener
这三个东西内部中,那么这个函数就是异步函数
还有其他 API 是异步的,遇到再说。
举例说明:
摇骰子, 随机打印出1-6 中的一个数
1 | function 摇骰子() { |
分析:
摇骰子()
里没有写 return,那就是 return undefined
箭头函数里有 return,返回真正结果
所以这是一个异步函数/异步任务。
1 | const n = 摇骰子(); |
如何拿到异步结果?
用回调,写个函数,然后把函数地址给他
1 | function f1(x) { |
简化为箭头函数
1 | function f1(x) { |
1 | // 面试题 |
异步总结
- 异步任务不能拿到结果
- 于是我们传一个回调给异步任务
- 异步任务完成时调用回调
- 调用的时候把结果作为参数
异步任务两个结果,成功或失败
两个方法解决
方法一:回调接受两个参数
1 | fs.readFile('./1.txt', (error, data) => { if(error){ console.log('失败'); return; } console.log(data.toString()) // 成功}) |
方法二:两个回调
1 | ajax('get', '/1.json', data => {/*成功回调*/}, error => {/*失败回调*/})ajax('get', '/1.json',{ success: () => {}, fail: () => {}})// 接受一个对象,对象有两个 key 表示成功和失败 |
这些方法的不足
不管方法一还是方法二,都有问题
- 不规范,名称五花八门,有人用 success + error,有人用 success + fail,done + fail
- 容易出现==回调地狱== ,代码变得看不懂
- 很难进行错无处理
回调地狱举例
1 | getUser( user => { getGroups(user, (groups) => { groups.forEach( (g) => { g.filter(x => x.ownerId === user.id) .forEach(x => console.log(x)) }) })})// 仅示例,这只是四层,二十层呢,代码会很难读 |
如何解决回调问题,用 Promise
有什么办法能解决这三个问题:
- 会犯回调的名字或顺序
- 拒绝回调地狱,让代码可读性更强
- 很方便地捕获错误
1976年,Daniel P.Friedman 和 David Wis 俩人提出 Promise 思想
后人基于此发明了 Future、Delay、Deferred等
前端结合 Promise 和 JS,制定了 Promise/A+规范
该规范详细描述了 Promise 的原理和使用方法。
以 AJAX 的封装为例,来解释 Promise
1 | // 示例ajax = (method, url, options) => { const {success, fail} = option; // 析构赋值 // const succes = option.success; // const fail = option.fail; const request = new XMLHttpRequest(); request.open(method, url); request.onreadystatechange = () => { if (request.readyState === 4) { // 成功就调用 success,失败 fail if (request.status < 400) { success.call(null, request.response); }else if (request.status >= 400) { fail.call(null, request, request.status); } } } request.rend();}ajax('get', '/xxx', { success(response){}, // function 缩写 // success: function(response){} fail: (request, status) => {}}) |
Promise 写法:
1 | // 先改一下调用姿势ajax('get', '/xxx', { success(response){}, fail: (request, status) => {}})// 上面用到了两个回调,还使用了 success 和 fail// 改成 Promise 写法ajax('get', '/xxx') .then((response)=>{}, (request, status)=>{} )// 虽然也是回调// 但是不需要经济 success 和 fail 了// then 的第一个参数就是 success// then 的第二个参数就是 fail |
请问 ajax() 返回了个啥 ?
返回了一个含有 .then() 方法的对象呗。
那么再请问如何得到这个含有 .then() 的对象呢 ?
那就要改造 ajax 的源码了。
return new Promise((resolve, reject) => {})
1 | // 修改 ajaxajax = (method, url, option) => { return new Promise((resolve, reject) => { const {success, fail} = option; const request = new XMLHttpRequest(); request.open(methon, url); request.onreadystatechange = () => { if(request.readyState === 4) { if(request.status < 400) { // 成功 resolve.call(null, request.response); }else if(request.status >= 400) { // s reject.call(null, request); } } } request.send(); });} |
小结
第一步:
retrun new Promise((resolve, reject) => {...})
- 任务成功则调用
resolve(result)
- 任务失败则调用
reject(error)
- resolve 和 reject 会再去调用成功和失败函数
第二步:
- 使用
.then(success, fail)
传入成功函数和失败函数
我们自己封装的 ajax 的缺点:
post 无法上传数据。request.send(这里可以上传数据)
不能设置请求头。request.setRequestHeader(key, value)
使用:
axios(推荐)