ES6中的Promise

ES6中的Promise

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

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對象時,還須要注意一些坑!筆者在使用時遇到了很多坑!

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

相關文章
相關標籤/搜索