异步专题:promise 和 async、await
1.同步和异步
同步和异步:同步和异步是一种消息通知机制
- 同步: A调用B,B处理获得结果,才返回给A。A在这个过程中,一直等待B的处理结果,没有拿到结果之前,需要A(调用者)一直等待和确认调用结果是否返回,拿到结果,然后继续往下执行。做一件事,没有拿到结果之前,就一直在这等着,一直等到有结果了,再去做下边的事
- 异步: A调用B,无需等待B的结果,B通过状态,通知等来通知A或回调函数来处理。 做一件事,不用等待事情的结果,然后就去忙别的了,有了结果,再通过状态来告诉我,或者通过回调函数来处理。如定时器等
2.ES6 Promise 对象
ES6的Promise对象是一个构造函数,用来生成Promise实例。
所谓Promise对象,就是代表了未来某个将要发生的事件(通常是一个异步操作)。
它的好处在于,有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数
- Promise的三种状态:pending 、resolve 和 reject
- then 方法 和 try/catch - 图片加载
then的返回值,会返回一个新的 Promise 对象, 但是状态会有几种情况:
- - then 的回调函数中没有返回值,then就会返回一个状态为: resolved 的 promise 对象
- - then 的回调函数返回值是 非 promise 的值, then就会返回一个状态为: resolved 的 promise 对象,另外会把返回值,传递给 下一个 then
- - then 的回调函数返回值是 promise 对象,then 就直接返回这个 promise 对象,具体的状态可以由我们自己定义,具体传递的值,也由我们自己定义
3.Promise 对象的三种状态
Promise的三种状态:pending 、resolved 和 rejected
- 创建Promise对象时,即没有执行成功也没有执行失败,则状态为pending;
- 创建Promise对象时,如果对象中是执行成功方法resolve(),则返回状态为resolved;
- 创建Promise对象时,如果对象中是执行失败方法reject(),则返回状态为rejected,且报错;
- 或者通过创建Promise 对象时,是否成功调用接口,从而获得Promise的响应状态
let p = new Promise((resolve,reject)=>{
resolve("执行成功");
// reject("执行失败");
});
//创建Promise对象时,即没有执行成功也没有执行失败,则状态为pending
// console.log(p);//Promise {<pending>}
//创建Promise对象时,如果对象中是执行成功方法resolve,则返回状态为resolved
console.log(p);//Promise {<resolved>: "执行成功"}
//创建Promise对象时,如果对象中是执行失败方法reject,则返回状态为rejected,且报错
// console.log(p);//Promise {<rejected>: "执行失败"}
4.Promise 对象的then方法
- then方法的有两个参数,都是函数;
- then方法的第一个参数表示执行成功后执行;
- then方法的第二个参数表示执行失败后执行;
- then方法可以调用多个(链式调用),上一个then的返回值决定下一个then是执行成功还是失败方法;
let p = new Promise((resolve,reject)=>{
// resolve("执行成功");
reject("执行失败");
});
//
p.then(info=>{
//第一个参数,执行成功时调用
console.log(info);//执行成功
},msg=>{
//第二个参数,执行失败时调用
console.log(msg);//执行失败
});
示例:图片下载
如果图片下载成功走第一个方法,如果图片下载失败走第二个方法
let p = new Promise((resolve,reject)=>{
let img = new Image();
img.src = "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1565026842061&di=796e2653d34dfe2c98a7b8538fae2aca&imgtype=0&src=http%3A%2F%2Fimages.cnblogs.com%2Fcnblogs_com%2Fidotnet8%2Fchina.gif";
img.onload = function(){
resolve("图片加载成功");
};
img.onerror = function(){
reject("图片加载失败");
};
});
//
p.then(success=>{
console.log(success);//图片加载成功
},error=>{
console.log(error);//图片加载失败
});
then的返回值,会返回一个新的 Promise 对象, 但是状态会有几种情况:
- then 的回调函数中没有返回值,then就会返回一个状态为: resolved 的 promise 对象。如果第一个then方法没有返回值,则无论这个then走的是成功还是失败方法,都不会影响下一个then方法的执行。
- then 的回调函数返回值是 非 promise 的值, then就会返回一个状态为: resolved 的 promise 对象,另外会把返回值,传递给 下一个 then
- then 的回调函数返回值是 自定义的promise 对象,then 就直接返回这个 promise 对象,具体的状态可以由我们自己定义,具体传递的值,也由我们自己定义(此时如果自定义的Promise对象返回resolve,会将成功信息传递给下一个then的第一个方法;如果自定义的Promise对象返回reject,会将失败信息传递给下一个then的第二个方法)。
- then没有返回值时(then没有返回值时,默认所有then方法同时执行):then就会返回一个状态为: resolved 的 promise 对象
let p = new Promise((resolve,reject)=>{
resolve("成功");
});
//then返回值一:如果then 方法没有返回值,就会返回一个状态为resolved的promise对象(即没有返回值默认走第一个成功的函数)
p.then(success=>{
console.log(success);//成功 第一个then方法没有返回值,所以默认走这个成功的方法
},error=>{
console.log(error);
}).then(info=>{
console.log(info);//undefined 由于第一个then没有返回值,而第二个then是根据第一个then的返回值接收信息的,所以info为undefined
});
- then有返回值,但是返回值是非promise的值时:then就会返回一个状态为: resolved 的 promise 对象,另外会把返回值,传递给 下一个 then
let p = new Promise((resolve,reject)=>{
resolve("成功");
});
//then返回值二:当then方法有返回值,但返回值时非promise对象时,直接将返回值传递给下一个then
p.then(success=>{
console.log(success);//成功
return success;
},error=>{
console.log(error);
}).then(info=>{
console.log(info);//成功 当then方法有返回值,但返回值时非promise对象时,直接将返回值传递给下一个then,所以这里的then方法得到的信息时第一个then的返回值
});
- then 的回调函数返回值是 promise 对象,then 就直接返回这个 promise 对象,具体的状态可以由我们自己定义,具体传递的值,也由我们自己定义
- 此时如果自定义的Promise对象返回resolve,会将成功信息传递给下一个then的第一个方法
- 如果自定义的Promise对象返回reject,会将失败信息传递给下一个then的第二个方法
let p = new Promise((resolve,reject)=>{
resolve("成功");
});
//then返回值三:当then方法有返回值,且返回值为Promise对象时,then方法直接返回这个Promise对象,具体的状态和值由我们自己决定
// 此时如果自定义的Promise对象返回resolve,会将成功信息传递给下一个then的第一个方法
// 如果自定义的Promise对象返回reject,会将失败信息传递给下一个then的第二个方法
p.then(success=>{
return new Promise((resolve,reject)=>{
// resolve("第一个then方法成功");
reject("第一个then方法失败");
});
},error=>{
console.log(error);
}).then(succMsg=>{
console.log(succMsg);
},errMsg=>{
console.log(errMsg);
});
5.Async 函数 和 await
- async函数和await方法,使用在await的各个方法都必须成功并且依赖执行的情况下。
- 如果async函数和await方法中,其中一个await执行失败,就不再执行之后的程序。
- 多个await方法中,最后一个await可以省略
需求:三个数据请求,请求1依赖请求2,请求2请求3。即请求1必须在请求2执行完成后才能执行,请求2必须在请求3执行完了才能执行
//Promise异步即以同步的方式实现异步
async function fn(){
await new Promise((resolve,reject)=>{
setTimeout(function(){
console.log("请求3");
resolve();
},1000);
});
await new Promise((resolve,reject)=>{
setTimeout(function(){
console.log("请求2");
resolve();
},1000);
});
await new Promise((resolve,reject)=>{
setTimeout(function(){
console.log("请求1");
resolve();
},1000);
});
}
fn();
async函数和await方法中,其中一个await执行失败,就不再执行之后的程序:
如下第二个await执行reject方法代表执行失败,执行失败后,请求1不会再继续执行
async function fn(){
await new Promise((resolve,reject)=>{
setTimeout(function(){
console.log("请求3");
resolve();
},1000);
});
await new Promise((resolve,reject)=>{
setTimeout(function(){
console.log("请求2");
reject();
},1000);
});
await new Promise((resolve,reject)=>{
setTimeout(function(){
console.log("请求1");
resolve();
},1000);
});
6.Promise 构造函数下的方法:resolve()、reject()、all()、race()
- Promise.resolve() 返回状态为resolved,且可自定义成功信息
- Promise.reject() 返回状态为rejected,且可自定义失败信息
- Promise.all([p1,p2,p3]) 所有promise都成功的情况下,才会执行的函数。Promise.all([p1,p2,p3])的返回值,再调用then方法无论有没有执行失败的方法,都是走第一个参数(成功)
- Promise.race() 无论执行失败还是成功,哪个方法先执行返回哪个方法的返回值。Promise.race([p1,p2,p3])方法的返回值,再调用then方法时,无论其中的方法执行成功还是失败,都是走then方法的第一个参数(成功)
6.1 Promise.resolve()和reject()方法:
{
let a = Promise.resolve("成功");
console.log(a);//Promise {<resolved>: "成功"}
try {
let b = Promise.reject("失败");
console.log(b);//Promise {<rejected>: "失败"}并且报错
throw "执行出错";
} catch (error) {
console.log(error);//执行出错
}
6.2 Promise.all([p1,p2,p3]) 所有promise都成功的情况下,才会执行的函数:
Promise.all([p1,p2,p3])的返回值,再调用then方法无论有没有执行失败的方法,都是走第一个参数(成功)
let p1 = new Promise((resolve,reject)=>{
setTimeout(function(){
resolve("p1执行成功");
},1000);
});
let p2 = new Promise((resolve,reject)=>{
setTimeout(function(){
// resolve("p2执行成功");
resolve("p2执行失败");
},1000);
});
let p3 = new Promise((resolve,reject)=>{
setTimeout(function(){
resolve("p3执行成功");
},1000);
});
//Promise.all([p1,p2,p3])只有所有的promise对象都是返回resolved状态,才会成功
let allP = Promise.all([p1,p2,p3]);
console.log(allP);
//Promise.all([p1,p2,p3])的返回值,再调用then方法无论有没有执行失败的方法,都是走第一个参数(成功)
allP.then(msg=>{
console.log("成功");
// console.log(msg);//(3) ["p1执行成功", "p2执行成功", "p3执行成功"]
console.log(msg);//(3) ["p1执行成功", "p2执行失败", "p3执行成功"]
},info=>{
console.log("失败");
});
6.3Promise.race() 无论执行失败还是成功,哪个方法先执行返回哪个方法的返回值
Promise.race([p1,p2,p3])方法的返回值,再调用then方法时,无论其中的方法执行成功还是失败,都是走then方法的第一个参数(成功)
//Promise.race([p1,p2,...])方法中那个方法执行的最快,就返回哪个方法的返回值
let p1 = new Promise((resolve,reject)=>{
setTimeout(function(){
resolve("p1执行成功");
},1000);
});
let p2 = new Promise((resolve,reject)=>{
setTimeout(function(){
// resolve("p2执行成功");
resolve("p2执行失败");
},500);
});
let p3 = new Promise((resolve,reject)=>{
setTimeout(function(){
resolve("p3执行成功");
},3000);
});
let raceP = Promise.race([p1,p2,p3]);
console.log(raceP);
//Promise.race([p1,p2,p3])方法的返回值,再调用then方法时,无论其中的方法执行成功还是失败,都是走then方法的第一个参数(成功)
raceP.then(msg=>{
console.log("成功");//成功
console.log(msg);//p2执行失败
},info=>{
console.log("失败");
console.log(info);
});
7.迭代器和Generator 函数
async await的底层是通过Generator逐渐演变过来的。现在有了async await方法,基本上Generator已经很少使用。但是一些底层的框架可能会用到。
7.1迭代
- 迭代协议:规定了迭代与实现的逻辑
- 迭代器:具体的迭代实现逻辑
- 迭代对象:可被迭代的对象 - 实现了[Symbol.iterator]方法
- 迭代语句
- for...in:以原始插入的顺序迭代对象的可枚举属性(迭代的是数组的key值)
- for...of:根据迭代对象的迭代器具体实现迭代对象数据(迭代的是数组的value值)
- 对象可以使用for...in 循环,但是不能使用for...of循环,因为对象不是可迭代对象,没有实现Symbol.iterator方法
迭代器实现原理 [Symbol.iterator] :
obj[Symbol.iterator] = function(){
return {
next(){
return {
value: this.i++,
done: false
}
}
}
}
for ...in 迭代的是数组的key值:
// for ...in 迭代的是数组的key值
var arr = [1,2,3,4,5];
for(var index in arr){
console.log(index);//0,1,2,3,4
}
for...of 实现迭代,迭代的是数组的value值:
// for ...of 迭代的是数组的value值
var arr = [1,2,3,4,5];
for(var val of arr){
console.log(val);//1,2,3,4,5
}
对象可以使用for...in 循环,但是不能使用for...of循环,因为对象不是可迭代对象,没有实现Symbol.iterator方法:
//对象使用for ...in循环
let obj = {
a:1,
b:2
};
for(let o in obj){
console.log(o);//a b
}
对象使用for...of循环直接报错:
//对象使用for ...of循环
let obj = {
a:1,
b:2
};
for(let val of obj){
console.log(val);//for...of迭代.html:22 Uncaught TypeError: obj is not iterable
}
7.2迭代器实现原理 [Symbol.iterator]
- 迭代协议:根据什么进行循环,循环中可以拿到什么东西,可以自己定义;
- 拥有这个迭代器的就是可迭代对象;
- 迭代器实现原理的格式是固定的;
let obj = {
a:1,
b:2
};
//自定义实现迭代器
obj[Symbol.iterator] = function(){
//注意获取values和keys的方法时Object构造函数下的方法
let vals = Object.values(obj);
let keys = Object.keys(obj);
let index = 0;
return {
next(){
if(index>=keys.length){
return {
done:true
}
}else{
//只想得到value值
// return {
// value: vals[index++],
// done:false
// }
//只想得到key值
// return {
// value: keys[index++],
// done:false
// }
//value值和key值想同时得到,就可以将value写成对象
return {
value: {
keys:keys[index],
//获取values时拖过keys获取,不要直接通过vals获取
values:obj[keys[index++]]
},
done:false
}
}
}
};
};
for(let val of obj){
console.log(val)
}
结果:
7.3Generator函数
在形式上,Generator是一个普通函数,但是有两个特征。
- 一是,function命令与函数名之间有一个星号。
- 二是,函数体内部使用yield语句,定义遍历器的每个成员,即不同的内部状态
-
调用Generator函数不会立即执行,而需要调用Generator函数的next()方法后才会执行
### Generator 语法
// Generator 语法
function* gen() {
yield 1;
yield 2;
yield 3;
}
//调用Generator函数不会立即执行
let g = gen();
console.log(g.next());//{value: 1, done: false}
7.4Generator函数和异步相关联
自执行Generator函数:
co函数:自定义自动化generator函数调用器。判断上一个异步执行完成后再执行下一个异步函数
function*fn(){
yield new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log("a");
resolve(1);
},500);
});
yield new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log("b");
resolve(2);
},500);
});
yield new Promise((resolve,reject)=>{
setTimeout(()=>{
console.log("c");
resolve(3);
},500);
});
}
co(fn);
function co(fn){
let f = fn();
next();
function next(msg){
let result = f.next();
if(!result.done){//done为true时表示走完了
result.value.then(info=>{
console.log(info,msg);//data表示上一步的返回信息
//上一个异步走完了,再执行下一个异步
next(info);
});
}
}
}
结果:
8.通过Promise实现链式动画(三种方式实现)
通过三种方式实现:回调地狱(不断重复调用);Promise的then方法;Promise的async/await方法
需求:自定义封装动画简易动画框架,让box 从左到右,再从上到下,再从右向左,再从下往上移动200px。
8.1回调地狱方式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>链式动画</title>
<style>
body {
margin: 0px;
padding: 0px;
position: relative;
}
#box {
width: 100px;
height: 100px;
background: red;
position: absolute;
left: 0;
top: 0;
}
</style>
</head>
<body>
<div id="box"></div>
<script>
{
//需求:自定义封装动画简易动画框架,让box 从左到右,再从上到下,再从右向左,再从下往上移动200px。
let box = document.querySelector("#box");
//需要给定参数,哪个元素,方向,移动到哪个位置
let move =(el,attr,target,cb)=>{
//获取元素起始位置
let start = parseFloat(getComputedStyle(el)[attr]);
//动画运动时,left或top有可能为往回走,就会是负方向
let dir = (target-start)/Math.abs(start-target);
let speed = 5;//元素移动的速度
let count = 0;//动画帧编号,用于取消requestAnimationFrame
//通过JS动画帧requestAnimationFrame让元素动起来
cancelAnimationFrame(count);
count = requestAnimationFrame(goTarget);
function goTarget(){
start += speed*dir;
//注意这里使用点会有问题
el.style[attr] = start + 'px';
//如果start等于了target就代表已经走完了,否则继续动画
if(start == target){
cancelAnimationFrame(count);
cb&&cb();
}else{
//注意动画编号在动画帧再次调用时仍然要记录
count = requestAnimationFrame(goTarget);
}
}
}
//回调地狱
//从左到右
move(box,"left",200,function(){
//从上到下
move(box,"top",200,function(){
// 从右向左
move(box,"left",0,function(){
move(box,"top",0,null);
});
});
});
}
</script>
</body>
</html>
8.2 then方法实现链式动画
//需求:自定义封装动画简易动画框架,让box 从左到右,再从上到下,再从右向左,再从下往上移动200px。
let box = document.querySelector("#box");
//需要给定参数,哪个元素,方向,移动到哪个位置
let move =(el,attr,target)=>{
//获取元素起始位置
let start = parseFloat(getComputedStyle(el)[attr]);
//动画运动时,left或top有可能为负
let dir = (target-start)/Math.abs(start-target);
let speed = 5;//元素移动的速度
let count = 0;//动画帧编号,用于取消requestAnimationFrame
// 使用Promise then方法必须返回Promise对象
return new Promise((resolve,reject)=>{
//通过JS动画帧requestAnimationFrame让元素动起来
cancelAnimationFrame(count);
count = requestAnimationFrame(goTarget);
function goTarget(){
start += speed*dir;
console.log(start);
//注意这里使用点会有问题
el.style[attr] = start + 'px';
//如果start等于了target就代表已经走完了,否则继续动画
if(start == target){
cancelAnimationFrame(count);
resolve();
}else{
//注意动画编号在动画帧再次调用时仍然要记录
count = requestAnimationFrame(goTarget);
}
}
});
}
//使用Promise 的then方法实现动画异步执行
move(box,"left",200)
.then(item=>{
//使用Promise 的then方法时,必须有return才能异步执行,否则全部then方法会同步执行
return move(box,"top",200);
})
.then(item=>{
return move(box,"left",0);
}).then(item=>{
move(box,"top",0);
});
8.3 async await方法实现链式动画
//需求:自定义封装动画简易动画框架,让box 从左到右,再从上到下,再从右向左,再从下往上移动200px。
let box = document.querySelector("#box");
//需要给定参数,哪个元素,方向,移动到哪个位置
let move =(el,attr,target)=>{
//获取元素起始位置
let start = parseFloat(getComputedStyle(el)[attr]);
//动画运动时,left或top有可能为负
let dir = (target-start)/Math.abs(start-target);
let speed = 5;//元素移动的速度
let count = 0;//动画帧编号,用于取消requestAnimationFrame
// 使用Promise then方法必须返回Promise对象
return new Promise((resolve,reject)=>{
//通过JS动画帧requestAnimationFrame让元素动起来
cancelAnimationFrame(count);
count = requestAnimationFrame(goTarget);
function goTarget(){
start += speed*dir;
//注意这里使用点会有问题
el.style[attr] = start + 'px';
//如果start等于了target就代表已经走完了,否则继续动画
if(start == target){
cancelAnimationFrame(count);
resolve();
}else{
//注意动画编号在动画帧再次调用时仍然要记录
count = requestAnimationFrame(goTarget);
}
}
});
}
//使用Promise 的then方法实现动画异步执行
async function runMove(){
await move(box,"left",200);
await move(box,"top",200);
await move(box,"left",0);
await move(box,"top",0);//最后一个await可以不写
}
runMove();