异步和回调
一、什么是回调
写了却不调用,给别人调用的函数,就是回调**
回调指你写好却不自己调用,送给其他人调用的函数。也就是我写好一个函数,告诉别人:“兄台,你回头(将来的某个时刻)调用一下这个函数呗”
回调可以传给函数当参数从而得到调用,也可以传给一个对象,让浏览器调用。
回调也可以是个对象,对象里面写上真正的回调函数。
举例
-
AJAX异步任务里的
request.onreadystatechange
就是个回调函数,传给1了XMLHttpRequest
对象,之后会给浏览器调用的。 -
我写了f1,f2调用了f1当他的参数,所以f1是回调。f1是回调,这个回调传给了一个函数当参数,从而得到调用
function f1(){}
function f2(fn){
fn()
}
f2(f1)
复制代码
- 我写了f1,f2调用了f1当他的参数,作用是把’你好’给f1当参数执行f1。f1是回调,这个回调传给了一个函数当参数,从而得到调用
function f1(x){ console.log(x)}
function f2(fn){
fn('你好')
}
f2(f1)
复制代码
二、什么是异步
如果能直接拿到结果,那就是同步
如果不能直接拿到结果,那就是异步
判断异步还是同步
如果一个函数的结果处于下面这些东西内部,这个函数就是异步函数
setTimeout
AJAX
(即XMLHttpRequest
) (注:AJAX只可以异步,设成同步请求页面会卡住)AddEventListener
举例———AJAX
const request = new XMLHttpRequest();
request.open("get", "/style.css"); //readyState=1
request.onreadystatechange = () => {
console.log(request.readyState);
//当statechange发生变化,就会调用这个函数
if (request.readyState === 4) {
console.log("下载完成");
if (request.status >= 200 && request.status < 300) {
const style = document.createElement("style");
style.innerHTML = request.response;
document.head.appendChild(style);
} else {
alert("加载CSS失败");
}
}
};
request.send();
复制代码
-
Ajax异步任务想要的结果是
response
,但是我们拿不到这个结果 -
在第四步
request.send();
后并不能立刻得到结果,因为request.send()
后readyState === 2
-
所以按照以前所学,我们必须还要等,等到
readyState === 4
,我们才能会得到结果response
。
那怎么拿到结果?用回调
= 在第三步,我们写了个回调函数request.onreadystatechange
传给了一个对象。我们想要的结果response
是该回调函数中的一个参数。
- 当Ajax异步任务完成了,结果
response
也有了,但这个结果必须让浏览器回头到第三步调用回调函数,拿到结果response
(或者对结果response
做回调函数函数体形式的变形) - 其实是每当
readyState
改变,浏览器就会回头调用回调函数request.onreadystatechange
一次,只不过是readyState
变成4时这个回调函数才会真正(对结果response
)做些什么
举例–摇骰子
function 摇骰子(){
setTimeout(
()=>{ return parseInt(Math.random() * 6) + 1}
,1000)
// return undefined
}
复制代码
摇骰子()
没有写return
,那就是return undefined
- 箭头函数里有
return
,返回真正的结果 - 所以
摇骰子()
是一个异步函数/异步任务
这个异步任务如何拿到结果?我们在这里通过回调获取结果
- 我们先写一个回调函数
function f1(x) { console.log(x) }
- 当异步任务完成时让异步任务调用回调函数当参数
摇骰子(f1)
- 那异步任务必须得定义为以函数为参数,不然回调函数怎么当异步任务的参数?
- 还有就是还得把结果当成回调函数的参数(也就是异步任务里的函数这个参数必须以结果当为参数,套娃的感觉起来了),然后在异步任务中执行这个回调函数
- 这样在第二步执行以回调函数为参数的异步任务时,就可以用回调函数的函数体的形式对结果进行操作返回了
function 摇骰子(fn){
setTimeout(
()=>{ fn( return parseInt(Math.random() * 6) + 1 )}
,1000)
// return undefined
}
复制代码
三、总结
1.异步
- 异步任务不能拿到结果
- 于是我们得写一个回调,结果是这个回调函数的参数,也就是回调函数会把结果变形和呈现出来
- 之后我们传把这个回调给异步函数当参数或者异步任务的一个对象的当属性
- 最后异步函数结束时会调用回调函数当参数或者异步任务完成时浏览器调用挂在对象上回调函数
- 注意在写异步函数/任务时,调用回调函数的时候必须把结果作为回调- 函数的参数,这样回调函数才会把结果变形和呈现出来
- 异步函数/任务完成时,异步函数/任务调用回调函数和执行回调函数,回调函数调用结果操作结果
2.异步与回调–只是合作关系,没有互相绑定的关系
- 异步任务需要用到回调函数来通知结果
- 但回调函数不一定只用在异步任务里
- 回调可以用到同步任务里
array.forEach( n => console.log(n) )
就是同步回调
promise———JS异步编程模型,目前前端解决异步问题的统一方案
1.promise起源
= 1976年,Daniel P. Friedman和David Wise俩人提出Promise思想
- 后人基于此发明了Future、Delay、 Deferred 等
- 前端结合(抄袭)Promise和JS,制订了Promises/A+规范,该规范详细描述了Promise 的原理和使用方法
2.为什么要用promise?
- promise好用
- 下面这些处理异步问题的方法有很多不足
- 不规范,成功回调和失败回调的名称五花八门,比如success+error、success+fail、done+fail
- 容易出现回调地狱,代码看起来很恐怖
- 很难进行错误处理
//如果结果有两个成功和失败,那怎么解决异步问题如何得到两个结果的问题
//方法一(node.js所有异步API都是这种方法!):还是用一个回调,之前回调只需要接受一个参数就是结果,现在这个回调要接受两个参数:失败的结果和成功的结果
//回调函数的作用流程:如果第一个参数error存在,也就是失败的结果存在,那就打印出失败,结束;如果第一个参数不存在,也就是没有失败,那么第二个参数也就是成功的结果data,我们对data变成字符串然后返回。
//readFile异步函数第一个参数是文件的路径,第二个参数就是回调函数。当异步函数执行完就会调用回调函数
fs.readFile('./1.txt', (error, data)=>{
if(error){ console.log('失败'); return }
console.log(data.toString()) //
})
//方法二:用俩个回调,一个回调以成功的结果为参数,操作并且返回;另一个回调一失败的结果作为参数,返回一些东西
ajax('get','/1.json', data=>{}, error=>{})
//方法三:使用一个回调对象,这个对象里面有两个真正的回调函数。异步任务完成后,当成功就调用success方法,失败就调用失败方法
ajax('get', '/1.json', {
success: ()=>{}, fail: ()=>{}
}
)
复制代码
3.promise初级用法(以封装Ajax为例)
- 首先上篇博客我学会了Ajax的四步步骤,太麻烦了,用一个简单的API把它封装起来吧
ajax = (method, url, options)=>{ //ajax是个异步函数,最后一个参数调用回调函数对象,当异步函数全部完成就会调用回调函数
const {success, fail} = options //回调函数对象里有俩函数success和fail ,单独拎出来,分别命名为success和fail
const request = new XMLHttpRequest()
request.open(method, url)
request.onreadystatechange = ()=>{ //第一重回调
if(request.readyState === 4){ //当异步任务全部完成后,我们拿不到结果,所以
if(request.status < 400){ //当结果是成功的
success.call(null, request.response) //就会调用success回调函数,并且把结果的response给该回调函数当参数,这样就可以对结果干些事情并且返回结果了
}else if(request.status >= 400){ //当结果是失败的
fail.call(null, request, request.status) //就会调用fail回调函数,并且把结果的request和status给回调函数当参数,这样就可以对结果干些事情并且返回点东西了
}
}
}
request.send()
}
//执行ajax异步函数
ajax('get', '/xxx', {
success(response){}, fail: (request, status)=>{}
}
)
//把成功和失败回调函数都写在回调对象里
复制代码
- 用promise改造上面的封装
ajax = (method, url) => {
return new Promise((resolve, reject) => {
//①返回一个promise对象,这个对象有个API叫then
//②Promise构造函数的参数是个函数
//③函数的参数有两个(resolve和reject),表示两个回调函数
//④其他的内容全部照常写
const request = new XMLHttpRequest()
request.open(method, url)
request.onreadystatechange = () => {
if (request.readyState === 4) { //异步函数完成会调用这个函数(一级回调)
if (request.status < 400) {
resolve.call(null, request.response) //结果是成功就调用resolve回调函数,并且参数是结果的response,这样就可以对结果干些事情并且返回了
} else if (request.status >= 400) {
reject.call(null, request) //结果是失败,就调用reject回调函数,并且参数是结果的request,这样就可以对结果干些事情并返回了
}
}
}
request.send()
})
}
//执行ajax异步函数
ajax('get', '/xxx').then((response)=>{}, (request, status)=>{})
//把加个then,成功回调函数和失败回调函数都写在里面
//成功回调函数对应的就是resolve,失败回调函数对应的就是reject(前面成功,后面失败,顺序不要弄错了)
复制代码
(1)用promise改造 封装后的ajax异步函数 具体步骤
- 返回一个promise对象
return new Promise((resolve, reject) => { })
- 其他内容全放在
{}
里 - 异步任务成功就是调用
resolve
回调函数(以结果为参数),失败就是调用reject
回调函数(以结果为参数)
(2)执行 用promise改造的封装后的ajax异步函数
执行异步函数的时候,就在后面加个.then(成功回调函数(结果),失败回调函数(结果))
,这样就传入了成功和失败回调函数了。
(3)一些零碎注意点
window.Promise
是一个全局函数,可以用来构造Promise
对象- 使用
return new Promise((resolve, reject)=> {})
就可以构造一个Promise
对象 - 构造出来的 Promise 对象含有一个
.then()
函数属性 resolve
和reject
可以改成任何其他名字,不影响使用,但一般就叫这两个名字- 任务成功的时候调用
resolve
,失败的时候调用reject
resolve
和reject
都只接受一个参数resolve
和reject
并不是.then(succes, fail)
里面的success
和fail
,resolve
会去调用success
,reject
会去调用fail
ajax.get('/xxx')
返回一个 Promise 对象ajax.get('/xxx').then(s, f)
在请求成功的时候调用s
,失败的使用调用f
Ajax库
用jQuery封装–jQuery.ajax
- 有兴趣就进入jQuery官方文档搜索ajax就可看到
- 支持更多形式的参数
- 支持Promise
- 支持的功能超多
- 但是我们不用需要掌握jQuery.ajax,现在的专业前端都在用axios
axios–目前最新的Ajax库
- 必看axios中文文档和Axios 作弊表(Cheat Sheet)
- 支持Promise
- 抄袭了jQuery的封装思路
axios.get('/xxx')
返回一个 Promise 对象axios.get('/xxx').then(s, f)
在请求成功的时候调用s
,失败的使用调用f
高级用法
- JSON自动处理
- axios如何发现响应的Content-Type是json,就会自动调用JSON.parse
- 所以说正确设置Content-Type是好习惯
- 请求拦截器
- 你可以在所有请求里加些东西,比如加查询参数
- 响应拦截器
- 你可以在所有响应里加些东西,甚至改内容
- 可以生成不同实例(对象)
不同的实例可以设置不同的配置,用于复杂场景