JS异步专题

it2024-11-17  23

异步专题: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 和 rejectthen 方法 和 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 对象,另外会把返回值,传递给 下一个 thenthen 的回调函数返回值是 自定义的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();

 

最新回复(0)