基础前端

Promise 结合 async 和 await

2019-07-19  本文已影响0人  CondorHero

上篇文章算是写了 Promise 的基础入门,这次结合 async 和 await 在对异步回调进行深入学习。

上篇文章: ES6 的 promise 和异步捕获错误 catch

一、Promise 深入学习

上篇文章写了 Promise 对异步回调黑洞的解救。但是却又陷入链式黑洞(或火车黑洞)。

  1. 例如我们用 node.js fs 模块依次打开六个 TXT 文件。
const fs = require("fs");

fs.readFile("./1.txt",function(err,data){
    console.log(data.toString());
    fs.readFile("./2.txt",function(err,data){
        console.log(data.toString());
        fs.readFile("./3.txt",function(err,data){
            console.log(data.toString());
            fs.readFile("./4.txt",function(err,data){
                console.log(data.toString());
                fs.readFile("./5.txt",function(err,data){
                    console.log(data.toString());
                    fs.readFile("./6.txt",function(err,data){
                        console.log(data.toString());
                    });
                });
            });
        });
    });
});
六首古诗

代码毕竟是给人看的,但上面的代码,结构松散,难于读取。

  1. 然后我们再用 Promise 改写。
const fs = require("fs");

function read(url){
    return new Promise(function(resolve,reject){
        fs.readFile(`./${url}.txt`,function(err,data){
            if(err)return;
            resolve(data.toString());
        });
    });
}
read(1).then(function(data){
    console.log(data);
    read(2).then(function(data){
        console.log(data);
        read(3).then(function(data){
            console.log(data);
            read(4).then(function(data){
                console.log(data);
                read(5).then(function(data){
                    console.log(data);
                    read(6).then(function(data){
                        console.log(data);
                    });
                });
            });
        });
    });
});

看了这个写法是不是要骂街了,这也没发挥出 Promise 作为语法糖的作用。所以如果是单纯异步回调改写成 promise 是没啥大区别。打败了回调黑洞进入链式黑洞。但是结合async 和 await 就大不一样了。

  1. 结合async 和 await 再次改写:
const fs = require("fs");

function read(url){
    return new Promise(function(resolve,reject){
        fs.readFile(`./${url}.txt`,function(err,data){
            if(err)return;
            resolve(data.toString());
        });
    });
}
async function main(){
    var data1 = await read(1);
    console.log(data1);
    var data2 = await read(2);
    console.log(data2);
    var data3 = await read(3);
    console.log(data3);
    var data4 = await read(4);
    console.log(data4);
    var data5 = await read(5);
    console.log(data5);
    var data6 = await read(6);
    console.log(data6);
}
main();

卧槽,这代码是绝了,结构简单逻辑清晰。看着就很舒服。代码最牛的是实现了 异步调用同步读取。而且省略了 then ,resolve 调用 then 里面的函数,函数的结果直接返回到等于号前面的 data 里面。

根据上面讲的可以发现 then 的功能被替代掉了,那还有没有 then 这个功能那?答案是有的比如我们依然依次打开三个 TXT 文件,每个文件里面都是一个 JSON 。

//a.txt
{"result":"I"}
=====
//b.txt
{"result":"Love"}
=====
//c.txt
{"result":"You"}

然后改写代码读取文件里面的 JSON 里面 result 的属性值。

const fs = require("fs");

function read(url){
    return new Promise(function(resolve,reject){
        fs.readFile(`./${url}.txt`,function(err,data){
            if(err)return;
            resolve(JSON.parse(data));
        });
    });
}
 async function main(){
    var data1 = await read("a").then(res=>res.result);
    console.log(data1);
    var data2 = await read("b").then(res=>res.result);
    console.log(data2);
    var data3 = await read("c").then(res=>res.result);
    console.log(data3);
}
main();
I Love You

可见 then 还是可以使用的,要想对返回的结果进行处理,then 是必要的。

解说上面的 async 和 await 例子:我们做一个主函数,比如叫做 main(),给这个函数加上 async 关键字,表示这个函数里面有异步的语句需要等待。异步语句的前面,加上 await 字样,表示这条语句是异步语句,等待该 Promise 对象的 resolve() 方法执行并且返回值后才能继续执行。

async 和 await 含义?

async 用于声明一个异步函数, 该函数需返回一个 Promise 对象. 而 await 通常后接一个 Promise对象, 需等待该 Promise 对象的 resolve() 方法执行并且返回值后才能继续执行. (如果await后接的是其他对象, 便会立即执行) await 只能用于 async 声明的函数上下文中

如果不加 await 我们答应的结果就是一个Promise 对象。


不加 await

如果不加 async ,直接报错。


不加async
<!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>canvas事件</title>
    <style>
        div {
            width: 60px;
            height: 60px;
            background-color: #0f0;
            margin:10px;
            position: relative;
        }
    </style>
</head>
<body>
    <div></div>
    <div></div>
    <div></div>
    <div></div>
</body>
</html>
<script src="./js/jquery-3.4.1.min.js"></script>
<script>
    $("div").eq(0).animate({"left":"600px"},2000);
    $("div").eq(1).animate({"left":"600px"},2000);
    $("div").eq(2).animate({"left":"600px"},2000);
    $("div").eq(3).animate({"left":"600px"},2000);
</script>

animate 是异步的所以会一起运动。



想要实现四个盒子依次运动就得用 callback 回调函数来解决。


回调结果
    $("div").eq(0).animate({"left":"600px"},2000,function(){
        $("div").eq(1).animate({"left":"600px"},2000,function(){
            $("div").eq(2).animate({"left":"600px"},2000,function(){
                $("div").eq(3).animate({"left":"600px"},2000);
            });
        });
    });

回调看着乱七八槽使用 Promise 代替

    function move(number){
        return new Promise(function(resolve,reject){
            // 注意这里的 resolve 函数,是作为回调函数调用的
            $("div").eq(number).animate({"left":"600px"},2000,resolve);
        });
    }
    move(0).then(function(){
        move(1).then(function(){
            move(2).then(function(){
                move(3).then(function(){});
            });
        });
    });

Promise 看着也不是太好,现在用异步语句来实现同步调用。

function move(number){
        return new Promise(function(resolve,reject){
            $("div").eq(number).animate({"left":"600px"},2000,resolve);
        });
    }
    async function main(){
        await move(0);
        await move(1);
        await move(2);
        await move(3);
    }
    main();

其实还可以在变,由Promise then 的写法变成 Generator 的 next 写法。generator 结合 async 使用。

function move(number){
        return new Promise(function(resolve,reject){
            $("div").eq(number).animate({"left":"600px"},2000,resolve);
        });
    }
    async function* main(){
        yield move(0);
        yield move(1);
        yield move(2);
        yield move(3);
    }
    var g = main();
    g.next();
    g.next();
    g.next();
    g.next();
二、Generator 的使用

形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。

Generator 函数和普通函数其实很像,调用方法和 Promise 的调用方法也很像,下面代码直接调用 main() 函数,会返回一个 Generator 对象,对象在打点调用 next 方法,yield 就像函数里面的 return 把自己后面的值返回,这时变量接收 a 得到的返回状态值是一个对象 {value: "0", done: false} ,很明显 value 后面是yield 后面的返回值,done 是表示函数体里面的语句是否全部执行完毕。现在 var a = g.next(); 只 next 函数了一个,函数现在暂停在第一个遇到的 yield 。继续 next 调用会开启第二个 yield 。

    function* main(){
        yield "0";
        yield "1";
        yield "2";
        yield "3";
    }
    //g 现在是 Generator对象
    var g = main();
    // a现在也是一个对象值为{value: "0", done: false}
    var a = g.next();
    console.log(g,a);

现在让函数体内的语句全部执行完。通过 while 去判断 res 对象里面的属性 done 的值。

function* main(){
        yield "0";
        yield "1";
        yield "2";
        yield "3";
    }
    //g 现在是 Generator对象
    var g = main();
    // a现在也是一个对象值为{value: "0", done: false}
    var res = g.next();
    // 遍历函数体
    while(!res.done){
        console.log(res.value);
        // 继续调用函数
        res = g.next();
    }

现在给函数体的开始加上 return 语句;这时返回结果value: 5, done: true} return 后面的内容被返回,且函数体全部语句显示执行完毕。

    function* main(){
        return 5;
        yield "0";
        yield "1";
        yield "2";
        yield "3";
    }
    //g 现在是 Generator对象
    var g = main();
    // a现在也是一个对象值为{value: "0", done: false}
    var res = g.next();
    console.log(res);

现在把 return 语句拿到函数体的最下面;这时返回结果:

0
1
2
3

很明显函数体执行完 return 的内容没有接收到。

    function* main(){
        yield "0";
        yield "1";
        yield "2";
        yield "3";
        return 5 ;
    }
    //g 现在是 Generator对象
    var g = main();
    // a现在也是一个对象值为{value: "0", done: false}
    var res = g.next();
    while(!res.done){
        console.log(res.value);
        res = g.next();
    }
上一篇下一篇

猜你喜欢

热点阅读