异步行为类似系统中断,使得不需要进程强制等待执行一个耗时操作
异步执行结果需要在更新完毕后通知其他代码,解决方法有以下三种
1.回调函数 CallBack Function
基本语法
以匿名函数作为回调函数为例
setTimeout (() => {
console.log("等待3s才会执行");
}, 3000 );
console.log("可以立即执行");
由于JS的运行机制是单线程异步执行程序,,所以上面的代码是在同一个主线程中运行的
回调地狱
假如说我们存在很多个函数,而c函数放在b函数里执行,d函数在c函数里面执行,e函数在d函数里面执行,以此类推,一直这样回调,那么回调函数就会形成一个回调链,而执行上下文的内存无法得到释放,一旦到了一个极限,就可能导致爆栈,就会形成回调地狱。
所以并不推荐通过回调函数解决异步问题
setTimeout (() => {
console.log("等三秒后");
setTimeout(() => {
console.log("再等三秒");
setTimeout(() => {
console.log("又等三秒");
//...
} ,3000)
}, 3000)
}, 3000 );
function a() {
console.log("a");
}
function b(c) {
setTimeout(() => {
console.log("b");
c();
}, 3000);
}
function c() {
console.log("c");
}
a();
b(c); //我们不直接调用c函数,我们通过给b函数传参,将它放在b()函数里面调用这种回调的方式去除异步的效果;
2.Promise 期约
2.1 简介
Promise支持链式调用, 可以解决回调地狱问题
基本流程:启动异步任务 => 返回Promise对象 => 给Promise对象绑定回调函数(甚至可以任务结束后指定多个回调)
2.2 基本语法
1.Promise构造函数:Promise(excutor){}
excutor函数:执行器(resolve,reject)=>{}resolve函数:内部定义成功时,调用value=>{}reject函数:内部定义失败时,调用reason=>{}
说明excutor会在Promise内部立即同步调用,异步操作在执行器中完成
//获取元素对象
const btn = document.querySelector("#btn");
//绑定单击事件
btn.addEventListener("click", function () {});
//promise形式实现,形参为两个函数
const p = new Promise((resolve, reject) => {
//同步调用,立即执行而不会进入队列中
setTimeout(() => {
let n = rand(1, 100);
if (n <= 30) {
resolve(n); //调用后将promise对象的状态设置为成功
} else {
reject(n); //失败时调用该函数
}
}, 2000);
});
//调用then方法
p.then((value) => {
//对象状态为成功时的回调
alert('中奖成功,中奖数字为'+value);
},(reason) => {
alert('中奖失败,失败数字为'+reason);
}
);
2.Promise.prototype.then方法:(onResolved,onRejected)=>{}
onResolved函数:成功的回调函数(value)=>{}onRejected函数:失败的回调函数(reason)=>{}
回调函数会返回一个新的Promise对象
3.Promise.prototype.catch方法: (onRejected)=>{}
onRejected函数:失败的回调函数(reason)=>{}
只可以指定失败的回调而不可以指定成功的回调
4.Promise.resolve方法:(value)=>{}
value可以为成功的数据或者Promise对象
//当传入参数为非Promise类型的对象,则返回的结果就是成功的Promise对象
let p1=Promise.resolve(521);
console.log(p1);
//若传入Promise类型的对象,则参数的结果决定了resolve的结果
let p2=Promise.resolve(new Promise((resolve,reject)=>{
resolve('OK'); //此时外面的Promise对象为成功的
reject('Error'); //此时外层的Promise对象为失败的
}));
5.Promise.reject方法:(reason)=>{}
不论传入什么都会返回一个失败的Promise对象
不可以通过try/catch捕获
6.Promise.all方法:(promises)=>{}
promises为包含n个promise对象的数组
返回一个新的promise,只有所有的promise都成功才成功,只要有一个失败就直接失败
let p1 = new Promise((resolve, reject) => {
resolve("OK");
});
let p2 = Promise.resolve("success");
let p3 = Promise.resolve("oh yeah");
const result = Promise.all([p1, p2, p3]);
console.log(result);
7.Promise.race方法:(promises)=>{}
promises为包含n个promise对象的数组
和上述方法不同,第一个完成的promise的结果状态就是最终的结果状态
2.3 基本原理
1.PromiseStata
promise的状态 PromiseStata(实例对象的属性),对应以下三个值:
- pending 未决定
- resolve / fullfilled 成功
- rejected 失败
每个promise对象的状态只能改变一次,而且只能从pending转化为resolve和从pending转化为rejected
2.PromiseResult
实例对象的另一个属性,保存失败或者成功的结果,只能通过以下函数进行修改
- resolve
- reject
2.4 promise的几个关键问题
1.改变promise状态的三个方法
let p1 = new Promise((resolve, reject) => {
//1.resolve函数 pending=>fulfilled
resolve("OK");
//2.reject函数 pending=>rejected
reject("error");
//3.抛出错误 pending=>rejected
throw "出问题了";
});
console.log(p1);
2.回调函数的执行
若一个promise对象指定多个成功/失败回调函数,当Promise对象状态改变时,都会执行
let p1 = new Promise((resolve, reject) => {
resolve("OK"); //Promise状态发生了改变
});
//指定回调 -1
p1.then(value=>{
console.log(value); //会执行
});
//指定回调 -2
p1.then(value=>{
alert(value); //会执行
});
指定回调函数then和回调函数value有结果不同,value有结果必须等待Prommise状态改变之后才可以
但是,指定回调函数then和Promise状态改变应该看执行顺序,如果一个延时较 长,就先指定另一个
let p1 = new Promise((resolve, reject) => {
resolve("OK"); //同步函数,resolve先执行
});
//回调函数
p1.then(value=>{
console.log(value);
});
let p1 = new Promise((resolve, reject) => {
setTimeout(()=>{ //异步函数,先指定then,但是value的结果需要等待resolve执行结束后才会有
resolve("OK");
},1000);
});
//回调函数
p1.then(value=>{
console.log(value);
});
2.5 自定义Promise
- 覆盖原先Promise定义
3.链式调用串联多个操作任务
通过then()的链式调用串联多个同步/异步任务
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("OK");
}, 1000);
});
p1.then((value) => {
return new Promise((resolve, reject) => {
resolve("success"); //此时返回的新promise是success,而不是OK
});
}).then((value) => {
console.log(value); //最终返回的是success
}).then((value)=>{
console.log(value); //返回undifined,因为这个then上面没有新Promise返回
});
4.异常穿透(只在链式调用会产生)
当使用then链式调用,可以在最后指定失败的回调
如果前面任何操作出了异常,都会传到最后失败的回调中处理
使用reject之后,将不会去执行then了,而是去执行catch
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
reject("ERROR");
}, 1000);
});
p1.then((value) => {
throw "失败啦";
})
.then((value) => {
console.log(222);
})
.then((value) => {
console.log(333);
})
.catch((reason) => {
console.log(reason); //输出ERROR而不是失败啦
});
5.中断Promise链式操作
只能通过创建promise,将状态改为pending才可以中断链式操作
let p1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("OK");
}, 1000);
});
p1.then((value) => {
console.log(111);
return new Promise(()=>{}); //此时便可以中断链式操作,不再打印222和333
})
.then((value) => {
console.log(222);
})
.then((value) => {
console.log(333);
});
3.async / await
异步函数是异步编程语法的终极解决方案,它可以让我们将异步代码写成同步的形式,让代码不再有回调函数嵌套,使代码变得清晰明了。
anync的设计初衷是简化promise的书写
简化结果见 blog.csdn.net/qq_39157944…
ES7提供的基于Promise之上的语法糖
const fn = async ()=>{}; //等同于
async function fn() {}
async关键字
- 普通函数定义前加async关键字 普通函数变成异步函数
- 异步函数默认返回promise对象
- 在异步函数内部使用return关键字返回结果 结果会被包裹在promise对象中 return关键字代替了resolve方法
- 在异步函数内部使用throw关键字抛出异常
- 调用异步函数再调用then方法获取异步函数执行结果
- 调用异步函数再调用catch方法获取异步函数执行的错误信息
async function fn() {
throw '发生了一些错误';
return 123;
}
fn().then((data)=>{
console.log(data);
}).catch(err=>{
console.log(err);
})
async关键字将函数标记为异步函数(返回值为Promise对象的函数),在异步函数中可以await语法用来调用异步函数。
await关键字
await关键字只能出现在异步函数中await promiseawait后面只能写promise对象 写其他类型的API是不可以的await关键字可暂停异步函数向下执行 直到promise返回结果
await会等待Promise完成之后直接返回最终的结果。这是因为await底层是基于Promise和事件循环Event Loop机制实现的
async function p1() {
return 'p1';
}
async function p2() {
return 'p2';
}
async function p3() {
return 'p3';
}
async function run() {
let r1= await p1();
let r2=await p2();
let r3=await p3();
console.log()
}
run();
Event Loop机制
await便用时的陷阱
- 这样写会破坏两个
fetch之间的并行关系
async function fun() {
const a = await fetch("https://jsonplaceholder.typicode.com/posts/1");
const b = await fetch("https://jsonplaceholder.typicode.com/posts/2");
}
应该改为:
async function fun() {
const promiseA = await fetch("https://jsonplaceholder.typicode.com/posts/1");
const promiseB = await fetch("https://jsonplaceholder.typicode.com/posts/2");
const [a, b] = await Promise.all([promiseA, promiseB]);
}
- 如果在循环中执行异步操作,是不能够直接调用
forEach或者map这一类方法的,尽管我们在回调函数中写了await,但这里的forEach会立刻返回,它并不会暂停等到所有异步操作都执行完毕。这种情况就得采用传统的for循环
async function fun() {
[1, 2, 3].forEach(async (i) => {
await someAyncOperation();
});
console.log("done");
}
- 若想要循环中的所有操作都并发执行,可以用
for await,这里的 for 循环依然会等到所有的异步操作都完成之后才继续向后执行
async function fun() {
const promises = [
someAsyncoperation(),
someAsyncQperation(),
someAsyncleration(),
];
for await (let result of promises) {
//...
}
console.log( "done ");
}
- 不能在全局或者普通函数中直接使用
await关键字,await只能被用在异步函数(async function)中