• ES6中的Promise


    ES6中的Promise

    JavaScript本身是单线程语言,这在Node.js学习中已经反复强调过,因为单线程,就需要在程序进行IO操作时做“异步执行”,比如最典型的网络操作——ajax的使用。

    在ajax请求网络时,就是在异步执行这个过程,等到异步工作执行完成,通过事先注册好的回调函数,异步事件完成立即触发回调函数,将回调函数拉入到同步执行中。

    可见,异步操作会在将来的某个时间点触发一个事件来调用某个函数(callback)。

    传统异步与回调函数

    在ES5的时代,使用回调函数,在异步代码执行完后,拉回“正轨”(同步到主线程中),例如以下的jquery ajax例子:

    $.post("/example/jquery/demo_test_post.asp",
        {
          name:"Donald Duck",
          city:"Duckburg"
        },
        function(data,status){
          alert("数据:" + data + "
    状态:" + status);
        });
    

    这段js代码做了一个post请求,并注册了请求结束后的回调函数,而这样写代码最大的两个问题是:1、不美观,2、回调函数不好重复使用。

    特别是在有多个异步操作且互相约束的情况下,就需要在回调函数中继续使用回调函数,导致callback地狱,写出一堆括号嵌套的代码出来。

    试试Promise

    Promise是ES6新增的标准,在ES5时代,已经有一些黑科技自己去模拟出这个Promise实现。

    什么是Promise?
    正如其翻译过来的字面意思“承诺”,Promise是代码和代码,函数和函数之间的承诺。
    来看个简单的Node.js数据库连接栗子:

    1、使用Promise建立一个公用连接池

    const mysql = require("mysql")
    const conn_info={
        host:"localhost",
        user:"root",
        password:"",
        database:"cslginfo"
    }
    
    function query(sql){
        let pool = mysql.createPool(conn_info);
        return new Promise(function(resolve,reject){
            pool.getConnection(function(err,conn){
                if(err)
                    reject("error connecting:"+err.stack);
                else{
                    conn.query(sql,function(err,res,fields){
                        if(err){
                            reject("error query");
                        }else{
                            resolve(res);
                        }
                    });
                }
            })
        })
    }
    

    这里使用Promise为异步操作结束后提供一个承诺的函数,意思是异步函数结束时你必须给个结果,要么获得了数据,要么中间出了错误,给出错误信息
    使用resolve表示成功的连接了,使用reject来给出错误,这种感觉有点像一个函数可以有多个返回。
    resolve将会触发链式操作的then,并将结果注入到函数变量参数中
    reject将会触发链式操作的catch,并将结果注入到函数变量参数中

    2、连接池的简单使用

    const sql="select * from student_base_info limit 10";
    query(sql).then(function(results){
    	//resolve给出的承诺在then中兑现
        console.log(results)
    }).catch(function(err){
    	//reject给出的承诺兑现在catch中
        console.log(err)
    })
    

    3、更高级!多表查询!

    上面那种情况并不能体现出Promise的特色,只不过是在then中传入一个function罢了,这和传统的回调大法传入function没有区别呀!

    那假设这种场景:我们查询学校某个学生的基本信息后,有另一张表对应了学生多条学历经历,我们查询某个学生的学历经历就是一种多表之间的依赖查询,第二次查询学历经历需要根据第一次查询学生的ID。如果还是使用传统的回调函数就会出现:回调函数中嵌套着下一层回调函数,这是为了等待当前查询结束触发下一次查询导致的,下一次查询的函数必须放在当次查询的函数里面。如果关联的表较多,代码写出来就会相当难看,基本就是这种鸟样子:

    query("select * form xxx where xxx=xxx",function(results){
        query("select * from xxx where ID="results[0].id,function(){
            ......
                query("sql",function(){
                    query("sql",function(){
                        ......
                    })
                })
        })
    })
    //每一次query查询都依赖上一次查询的结果,这就很难受了
    

    但Promise的then.then.then链式查询可以将这么多查询串成一个“烤串串”,而不必层层嵌套

    //多表关联查询
    query(`select * from student_base_info where 姓名 = '黄有为'`).then(res=>{
        return query(`select * from student_school_info where 学号 = '${res[0].学号}'`)
    }).then(res=>{
        console.log(res);
    }).catch(err=>{
        console.log(err);
    });
    

    像这样的链式代码就显得十分优雅舒适了,就好像一个个诺言在一个个往下实现,用专业的术语说就是:Promise的then把异步操作同步化了。
    上述js代码所做的工作是,查询一个名叫“黄有为”的人,并从另一张表中根据他的学号查出他上学的经历,最终效果如下所示:

    查询结果

    4、别干傻事!
    千万千万要注意,不要在then中再嵌套then,这就没有意义了,还不如写你的“回调地狱”去!
    不要写出这种代码来:

    这里写图片描述

    关于Promise一些坑

    在使用Promise对象时,还需要注意一些坑!笔者在使用时遇到了不少坑!

    1、同一个Promise不能多次使用!
    例如:

    let num = 0;
    let p = new Promise(function(resolve){
        resolve(num);
    })
    
    while(num<10){
        p.then(res=>{
            console.log(res);
        });
        num++;
    }
    

    p是同一个Promise对象,其中代码只会执行一次,故执行后:

    这里写图片描述

    我们发现resolve所给出的res结果没有变过,说明之后9次都会输出第1次的res结果,承诺已经兑现,不再执行!结论:同一个Promise对象只兑现一次“承诺”。

    解决方案:Promise工厂函数

    对上面代码稍作修改:

    let num = 0;
    let p = function(num){
        return new Promise(function(resolve){
            resolve(num)
        });
    }
    
    while(num<10){
        p(num).then(res=>{
            console.log(res);
        });
        num++;
    }
    

    控制台输出:

    这里写图片描述

    工厂模式除了能解决多个Promise的问题,还为Promise执行提供输入参数。

    2、多个resolve,reject只传值一次,后面代码依然要执行?

    当多个resolve,reject混合出现在一个逻辑当中,执行到第一个resolve或reject就会返回到then的函数中。但是!注意但是!!!这些resolve,reject之后的代码依然会执行!也就是说resolve,reject不能看成是return或者throw操作,他返回一些变量但是却不结束代码段。做几个测试,修改1中一些代码:

        return new Promise(function(resolve,reject){
            resolve("same");
            resolve(num);
            reject("error");
            throw(new Error("ss"))
            console.log("test");
        })
    

    上述代码中test不会打印,因为throw结束了函数,换成return也是一样的效果,而then实际接收到的变量应该是“same”,后面的resolve并不会覆盖第一个,reject也不会覆盖,但是他们都是会执行的!

    效果:

    这里写图片描述

    如下代码:

        return new Promise(function(resolve,reject){
            resolve("same");
            resolve(num);
            reject("error");
            console.log("test");
        })
    

    其中的console.log()就是要执行的,效果:

    这里写图片描述

    最后,一个小疑问。如果是多层的多对多数据库查询呢?

    想象下这种场景,我从表1中读取了10条数据,再依据这10条,每条从表2中读取相关的10条(最后应该是100条),问题不在于最后到底几条数据,而在于读完第一个10条之后不知道如何通过链式查询读与这10条相关的100条数据,因为这种查询是一个树状查询,但Promise的then是种链式查询,无论是逻辑上还是物理上都不好通过Promise和then来实现这种查询,当然你可以通过工厂模式在第一次查询出10条数据(乃至n条)后生产n个Promise对象继续往下模拟出树状查询,但这实现起来很麻烦,并且很难管理这么多的Promise。

    最好的办法是通过ES7中的async和await来完成异步化同步的转变!

    async,await下一章再说,累了,休息了,祝自己生日快乐!

  • 相关阅读:
    LeetCode100-相同的树
    LeetCode66-加一
    LeetCode102-二叉树的层序遍历
    dubbo协议端口
    http错误-413 Request Entity Too Large
    【Jeecg Vue】通过getAction的finally来最大程度避免影响主数据呈现
    图片压缩,用这个就够了
    信息数据安全,日常办公你就要注意这些!
    java笔记:流式编程 数组与List集合互转
    改状态,你会改吗?你真的会改吗?
  • 原文地址:https://www.cnblogs.com/devilyouwei/p/8921112.html
Copyright © 2020-2023  润新知