1、什么是异步编程?
异步编程允许我们在执行一个长时间任务时,程序不需要等待,而是继续执行之后的代码,直到这些任务完成后再回来通知你 ,通常是以回调函数(callback)的形式,这种模式避免了的程序的堵塞,大大提高了cpu的执行效率。
2、在JavaScript的两种实现异步的方式
2.1、第一种:回调函数
比如我们可以使用setTimeout()让一个函数在指定的时间后执行,这个函数本身会立刻返回,程序紧接着会执行之后的代码,而我们传入的回调函数则会等到预定的时间才会执行
setTimeout()=>{ //回调函数
console.log("灰太狼");
},3000);
console.log("你会立刻看到我");
2.1.1、回调函数的缺点
如果需要依次执行多个异步操作,整个程序会一层接着一层的嵌套下去,可读性会变得非常差,这种情况也被叫做函数的回调地狱,为了解决这一问题,Promise应运而生
setTimeout()=>{
console.log("三秒后");
setTimeout()=>{
console.log("三秒后");
setTimeout()=>{
console.log("三秒后");
},3000);
},3000);
},3000);
2.2、第二种promise
JavaScript中使用Promise的API,fetch()就是一个很好的例子,它用来发起一个请求来获取服务器数据,可以用它动态的更新页面内容,也就是平时说的AJAX技术
这里调用fetch()去访问这个测试地址的数据 http://jsonplaceholder.typicode.com/posts
可以看到fetch()立刻返回了一个Promise对象
随后可以调用他的then方法并传递一个回调函数
fetch("http://jsonplaceholder.typicode.com/posts/1")
//参数
.then((response)=>{//如果在未来成功完成,函数会被调起,请求的结果也会以参数的形式传递进来
});
2.2.2、Promise的优点
它可以用一种链式结构将多个异步操作串联起来
fetch("http://jsonplaceholder.typicode.com/posts/1")
.then((response)=>response.json())
//response.json也会返回一个Promise,
//他代表在未来的某一个时刻将返回的数据转换成JSON格式
.then((json)=>console.log(json));
如果我们想要等到他完成以后在执行其他操作,可以在她后面追加一个.then
Promise的链式调用避免了代码的层层嵌套,即便有一个很长的链,代码也是向下增长,而并非向右,因此可读性会提升很多
fetch("http://...")
.then(...)
.then(...)
.then(...)
.then(...);
在过程中,会遇到很多错误,我们可以来捕获这些错误,可以附加一个catch,在链式结构的末尾
fetch("http://jsonplaceholder.typicode.com/posts/1")
.then((response)=>response.json())
.then((json)=>{
console.log(json);
})
.catch(error)=>{
console.error(error);
});
如果之前任意一个阶段发生了错误,catch将会被触发,then()将不会被执行
类似的Promise还提供finally方法,他会在Promise链结束之后调用,可以做一些清理工作
fetch("http://jsonplaceholder.typicode.com/posts/1")
.then((response)=>response.json())
.then((json)=>{
console.log(json);
})
.catch(error)=>{
console.error(error);
})
.finally(()=>{
//执行清理操作
});
如果用到了加载动画,可以在finally中关闭它
.finally(()=>{
stopLoadingAnimation();
})
3、async、await
首先我们需要使用async关键字将函数标记为异步函数
async function f(){
const response = await fetch("http://...");
//写了async,就不再需要then(),而是使用await语法
//await会等待Promise完成之后直接返回最终结果
const json = await response.json();
console.log(json);
}
f();
await虽然看上去会暂停其他函数的执行,但在等待的过程中JS同样可以处理其他任务,这是因为await的底层是基于Promise和循环事件机制实现的
3.1、await的使用陷阱
3.1.1、第一种
这样写不存在逻辑上的错误,但是会打破fetch()操作的并行
async function f(){
const a = await fetch("http://...");
const b = await fetch("http://...");
}
f();
正确做法,将所有的Promise用Promise.all组合起来
async function f(){
const promiseA = await fetch("http://...");
const promiseB = await fetch("http://...");
const [ a,b] = await Promise.all([promiseA,promiseB]);
}
3.2.2、第二种
如果想在异步中做执行的操作是不能直接调用forEach或者map这一类的方法的,应该用for循环
async fuction f(){
for (let i of[1,2,3]){
await someAsyncOperation();
}
console.log("done");
}]
f();
如果我们想要循环中的操作都并发执行,可以使用for await
async fuction f(){
const promise=[
somAsyncOperation(),
somAsyncOperation(),
somAsyncOperation(),
];
for await(let result of promises){
}
console.log("done");
}
f();
3.3.3、第三种
不可以在全局或者普通函数中使用await关键字,如果想在最外层使用await,可以先定义一个异步函数,再使用await
(async()=>{
await somAsyncOperation();
})();
一些旧版本的浏览器不支持await ,async语法,可以通过转译器编译成兼容的等效代码