zoukankan      html  css  js  c++  java
  • 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下一章再说,累了,休息了,祝自己生日快乐!

  • 相关阅读:
    Python 学习笔记 11.模块(Module)
    Python 学习笔记 8.引用(Reference)
    Python 学习笔记 9.函数(Function)
    Python 学习笔记 6.List和Tuple
    Python 学习笔记 4.if 表达式
    Python 学习笔记 2.自省
    Python 学习笔记 3.简单类型
    Python 学习笔记 7.Dictionary
    Python 学习笔记 5.对象驻留
    Python 学习笔记 10.类(Class)
  • 原文地址:https://www.cnblogs.com/devilyouwei/p/8921112.html
Copyright © 2011-2022 走看看