JS异步专题

异步专题: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 对象, 但是状态会有几种情况:

  1. - then 的回调函数中没有返回值,then就会返回一个状态为: resolved 的 promise 对象
  2. - then 的回调函数返回值是 非 promise 的值, then就会返回一个状态为: resolved 的 promise 对象,另外会把返回值,传递给 下一个 then
  3. - then 的回调函数返回值是 promise 对象,then 就直接返回这个 promise 对象,具体的状态可以由我们自己定义,具体传递的值,也由我们自己定义

3.Promise 对象的三种状态

Promise的三种状态:pending 、resolved 和 rejected

  1. 创建Promise对象时,即没有执行成功也没有执行失败,则状态为pending;
  2. 创建Promise对象时,如果对象中是执行成功方法resolve(),则返回状态为resolved;
  3. 创建Promise对象时,如果对象中是执行失败方法reject(),则返回状态为rejected,且报错;
  4. 或者通过创建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方法

  1. then方法的有两个参数,都是函数;
  2. then方法的第一个参数表示执行成功后执行;
  3. then方法的第二个参数表示执行失败后执行;
  4. 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 对象, 但是状态会有几种情况:

  1.  then 的回调函数中没有返回值,then就会返回一个状态为: resolved 的 promise 对象。如果第一个then方法没有返回值,则无论这个then走的是成功还是失败方法,都不会影响下一个then方法的执行
  2.  then 的回调函数返回值是 非 promise 的值, then就会返回一个状态为: resolved 的 promise 对象,另外会把返回值,传递给 下一个 then
  3. 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 对象,具体的状态可以由我们自己定义,具体传递的值,也由我们自己定义
  1.  此时如果自定义的Promise对象返回resolve,会将成功信息传递给下一个then的第一个方法
  2. 如果自定义的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

  1. async函数和await方法,使用在await的各个方法都必须成功并且依赖执行的情况下。
  2. 如果async函数和await方法中,其中一个await执行失败,就不再执行之后的程序。
  3. 多个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()

  1. Promise.resolve() 返回状态为resolved,且可自定义成功信息
  2. Promise.reject() 返回状态为rejected,且可自定义失败信息
  3. Promise.all([p1,p2,p3]) 所有promise都成功的情况下,才会执行的函数。Promise.all([p1,p2,p3])的返回值,再调用then方法无论有没有执行失败的方法,都是走第一个参数(成功)
  4. 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迭代

  1. 迭代协议:规定了迭代与实现的逻辑
  2. 迭代器:具体的迭代实现逻辑
  3. 迭代对象:可被迭代的对象 - 实现了[Symbol.iterator]方法
  4. 迭代语句
  • 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是一个普通函数,但是有两个特征。

  1. 一是,function命令与函数名之间有一个星号
  2. 二是,函数体内部使用yield语句,定义遍历器的每个成员,即不同的内部状态
  3. 调用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();
发布了95 篇原创文章 · 获赞 115 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/qq_34569497/article/details/98392151