1 同步与异步
1.1 概念
同步:能直接拿到结果,比如在医院挂号,只有拿到号才离开窗口;
异步:如果 JS 不能直接拿到一个函数的结果,可以先去执行别的代码,等结果到了再取结果,就是异步。比如餐厅门口等位,拿到号后可以去逛街,想要拿到结果,可以通过每10分钟去餐厅询问一次(轮询),或者扫码用微信接收通知(回调)
轮询:定时去询问结果拿到了没有;
回调:异步的结果可以通过回调获取,一般来说结果会作为回调的第一个参数;
异步的好处是可以把用来等待的时间拿去做别的事情;
1.2 异步举例
- AJAX 1、request.send()之后,并不能直接得到 response ,此时console.log(request.response) 没有值;
2、必须等到 readyState 变为 4 后,浏览器回头调用 request.onreadystatechange 函数;
3、此时才能得到 request.response ;
1.3 回调
1、写了却不调用,给别人调用的函数,就是回调 callback,回调除了用函数形式,还可以用对象形式,如 request.onreadystatechange;
2、例如:request.onreadystatechange 就是自己写给浏览器调用的,意思就是 你(浏览器)回头调一下这个函数;
3、例子
function f1(){} //写了 f1 函数,自己没有调用;
function f2(fn){
fn() // f2 调用了 f1,所以 f1 是回调函数;
}
f2(f1)
function f1(){} //写了 f1 函数,自己没有调用;
function f2(fn){
// fn() 此时 f2 也没有调用 f1,所以 f1 不是回调函数;
}
f2(f1)
1.4 异步与回调的关系
- 关联 1、异步任务需要在得到结果时通知 JS 来拿结果;
2、通知方式是,让 JS 留一个函数地址(电话号码)给浏览器;
3、异步任务完成时浏览器调用该函数地址即可(拨打电话);
4、同时把结果作为参数传给该函数(电话里说可以来吃了);
5、这个函数是我写给浏览器调用的,所以是回调函数;
- 区别 1、异步任务一般用回调函数来通知结果,也可以用轮询;
2、回调函数不一定只用在异步任务里,还可以用在同步任务里;
3、例如:array.forEach(n=>console.log(n)) 就是同步回调;
2 判断同步与异步
- 如果一个函数的返回值处于 1、setTimeout
2、AJAX(即 XMLHttpRequest)
3、AddEventListener
这三个东西内部,那么这个函数就是异步函数;
-
AJAX 通过第三个参数可以设为同步的,但是不要这么做,不然会在请求期间使页面卡住;
request.open('GET','/2.js',true/false) // 默认为true, 改为 false 时即为同步 -
例子 1、摇骰子
function 摇骰子(){
setTimeout(()=>{
return parseInt(Math.random()*6) + 1
},1000)
}
// 摇骰子()没有写 return ,就是 return undefined ;
// 箭头函数里有 return ,返回真正的结果,所以是一个异步函数/异步任务。
2、摇骰子续
const n = 摇骰子()
console.log(n) // 返回 undefined
怎么拿到异步结果:
1、用回调,写个函数,然后把函数地址给它
2、摇骰子函数得到结果后,将结果作为菜蔬传给 f1 ;
function f1(x){ console.log(x) }
摇骰子(f1)
function 摇骰子(fn){
setTimeout(()=>{
fn(parseInt(Math.random()*6) + 1)
},1000)
}
3、简化函数 由于 fa 声明之后只用了一次,所以可以删掉 f1
function f1(x){console.log(x)}
摇骰子(x)
改为:
摇骰子(x =>{console.log(x)}
再减化为:
摇骰子(console.log} // 参数个数一致时才能这样简化
4、参数个数不一致时,不能简化;
const array = ['1','2','3'].map(parseInt) // 简化
console.log(array) // [1, NaN, NaN]
因为 map() 接收三个参数,如果简化,则 map(parseInt) 相当于 map((item,i,arr1)=>{ return parseInt(item,i,arr1)}),而 parseInt() 只接受两个参数,即参数不一致,所以不能简化。
第一次:parseInt('1',0) ,给 parseInt 传0,相当于没有传;
第二次:parseInt('2',1), 将 2 作为 1 进制的树进行解析,所以是 NaN ;
第三次:parseInt('3',2), 将 3 作为 2 进制的进行解析,所以是 NaN ;
const array1 = ['1','2','3'].map((item,i,arr1)=>{ return parseInt(item)})
console.log(array1) // [1, 2, 3]
- 总结 1、异步任务不能拿到结果;
2、于是传一个回调给异步任务;
3、异步任务完成时调用回调;
4、调用的时候把结果作为参数;
3 异步任务有两个结果:成功或失败
3.1 方法一:回调接受两个参数
fs.readFile('./1.txt',(error,data)=>{
if(error){ console.log('失败'); return }
console.log(data.toString()) //成功
}
3.2 搞两个回调
ajax('get','/1.json', data=>{}, error=>{})
//前面是成功回调,后面是失败回调
或者
ajax('get','/1.json',{
success: ()=>{},
fail: ()=>{}
})
接受一个对象,对象有两个 key 表示成功和失败
3.3 缺点
1、不规范,有人用 success + error, 有人用 success + fail, 有人用 done + fail ;
2、容易出现回调地狱;
getUser( user =>{
gerGroups(user, (groups)=>{
groups.forEach((g)=>{
g.fileter(x => x.ownerId === user.id)
.forEach( x => console.log(x))
})
})
})
3、很难进行错误处理;
4 Promise
Promise - JavaScript | MDN (mozilla.org)
-
Promise 是目前前端解决异步问题的统一方案
-
window.Promise 是一个全局函数,可以用来构造 Promise 对象;
-
使用 return new Promise((resolve,reject)=>{}) 可以构造一个 Promise 对象;
-
构造出来的 Promise 对象含有一个 .then() 函数属性;
-
Promise 函数本身不含有一个 .then() 函数属性;
-
Promise 不可以取消,axios可以通过 cancelToken 取消,但取消的不是 Promise,而是发送的那个请求;
-
以封装 AJAX 为例
ajax = (method,url,options)=>{
const {success, fail} = options
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.send()
}
ajax('get','/xxx',{
success(response){},
fail:(request,request.status)=>{}
}
- 将上面代码改成 Promise 写法
// 先改调用方式
ajax('get','/xxx',{
success(response){},
fail: (request,status)=>{}
}) //这里用到了两个回调,还使用了 success 和 fail
// 改成 Promise 写法
ajax('get','/xxx')
.then((response)=>{},(request,status)=>{})
// 虽然也是回调,但是不需要记 success 和 fail 了
// then 的第一个参数就是 success, 第二个参数就是 fail;
// 这里 ajax 返回了一个含有 .then() 方法的对象,需要改造 ajax 的源码,才能得到这个对象;
ajax = (method, url, options)=>{
return new Promise((resolve,reject)=>{
const {success, fail} = options
const request = new XMLHttpRequest()
request.open(method,url)
request.onreadystatechange = ()=>{
if(request.readyState === 4){
//成功就调用 success, 失败就调用 fail
if(request.status < 400){
resolve.call(null,request.response)
}else if(request.status >= 400){
reject.call(null,request,request.status)
}
}
}
request.send()
})
}
- 小结 1、return new Promise((resolve,reject)=>}{});
2、任务成功则调用 resolve(result);
3、任务失败则调用 reject(result),resolve、reject 都只接受一个参数;
4、resolve 和 reject 不是 .then(success,fail)里面的 success 和 fail,resolve 调用成功函数,reject 调用失败函数;
5、使用 .then(success,fail) 传入成功函数和失败函数
- 上面封装的 ajax 的缺点 1、post 无法上传数据,request.sen(这里可以上传数据)
2、不能设置请请求头, request.setRequestHeader(key,value)
3、解决方法:使用 jQuery.ajax 或 axios
- jQuery.ajax 1、jQuery.ajax()
2、优点:支持更多形式的参数,支持Promise,支持超多功能;
- axios axios中文文档
1、目前最新的 AJAX 库;
2、axios.get('/xxx') 返回一个 Promise 对象, axios.get('/xxx').then(s,f) 在请求成功的时候调用 s, 失败的时候调用 f ;
3、高级用法:
JSON自动处理:axios 如果发现响应的 Content-Type 是 json,就会自动调用 JSON.parse;
请求拦截器:可以在所有请求里加东西,比如查询参数;
响应拦截器:可以在所有响应里加东西,甚至改内容;
可以生成不同实例(对象):不同的实例可以设置不同的配置,用于复杂场景;