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下一章再說,累了,休息了,祝本身生日快樂!