JavaScript自己是单线程语言,这在Node.js学习中已经反复强调过,由于单线程,就须要在程序进行IO操做时作“异步执行”,好比最典型的网络操做——ajax的使用。mysql
在ajax请求网络时,就是在异步执行这个过程,等到异步工做执行完成,经过事先注册好的回调函数,异步事件完成当即触发回调函数,将回调函数拉入到同步执行中。jquery
可见,异步操做会在未来的某个时间点触发一个事件来调用某个函数(callback)。es6
在ES5的时代,使用回调函数,在异步代码执行完后,拉回“正轨”(同步到主线程中),例如如下的jquery ajax例子:ajax
$.post("/example/jquery/demo_test_post.asp", { name:"Donald Duck", city:"Duckburg" }, function(data,status){ alert("数据:" + data + "\n状态:" + status); });
这段js代码作了一个post请求,并注册了请求结束后的回调函数,而这样写代码最大的两个问题是:一、不美观,二、回调函数很差重复使用。sql
特别是在有多个异步操做且互相约束的状况下,就须要在回调函数中继续使用回调函数,致使callback地狱,写出一堆括号嵌套的代码出来。数据库
Promise是ES6新增的标准,在ES5时代,已经有一些黑科技本身去模拟出这个Promise实现。promise
什么是Promise?
正如其翻译过来的字面意思“承诺”,Promise是代码和代码,函数和函数之间的承诺。
来看个简单的Node.js数据库链接栗子:网络
一、使用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,并将结果注入到函数变量参数中async
二、链接池的简单使用
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) })
三、更高级!多表查询!
上面那种状况并不能体现出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代码所作的工做是,查询一个名叫“黄有为”的人,并从另外一张表中根据他的学号查出他上学的经历,最终效果以下所示:
四、别干傻事!
千万千万要注意,不要在then中再嵌套then,这就没有意义了,还不如写你的“回调地狱”去!
不要写出这种代码来:
在使用Promise对象时,还须要注意一些坑!笔者在使用时遇到了很多坑!
一、同一个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执行提供输入参数。
二、多个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下一章再说,累了,休息了,祝本身生日快乐!