1. 程式人生 > >ES6中的Promise

ES6中的Promise

什麽 while oca pan 那種 高級 mysq 最終 入參

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 + "\n狀態:" + 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下一章再說,累了,休息了,祝自己生日快樂!

ES6中的Promise