天天看點

Promise你把握不住?來看看異步轉同步改造看看Promise吧總結

前言

先說一下上一篇NodeJS文章

NodeJS 後端開發 06 連接配接MySQL

,這一篇展示了一個資料庫連結的db.js 工具庫。

該工具庫提供了一個runSql函數,它的運作方式是先送出SQL,然後再異步把查詢結果回調傳遞給callback函數的。

這個函數隻适用于提前加載資料的情況,比如預先緩存批量的查庫結果。然後使用者請求WebAPI的時候,直接讀查詢緩存。

這樣會有下面的問題:

新資料入庫了,本地緩存需要更新。

而且無法支援動态查詢。

說這麼多,直接用Koa做個接口示範一下查詢效果吧。

代碼展示,KOA做接口查詢資料庫

const koa = require('Koa');                                                 
const app = new koa()

var router = require('koa-router')();
var db = require('./db-lite');

//做個簡單接口
router.get('/', function(ctx, next){ctx.body='Hello, geeklevin!'});
//做個接口查詢資料庫
router.get('/query', function(ctx,next){
    db.runSql("SELECT * FROM COMPANY; ", function(result,fields){
        console.log('查詢結果:', result);
        ctx.body = JSON.stringify(result);
        console.log('設定響應body成功!');
    });
    console.log('processing');
});
//啟動伺服器
app.use(router.routes());
const PORT = 8081
console.log('start app at port %s', PORT)
app.listen(PORT);
      

先檢視簡單接口展示效果:

Promise你把握不住?來看看異步轉同步改造看看Promise吧總結

再看看/query接口

日志看到設定了響應體ctx.body成功了,可這時候請求已經結束了。

因為db.runSql函數不是同步執行了,/query這個接口執行這個函數,碰到函數結束了,目前請求處理完畢,沒有設定請求體,是以響應Not Found。

Promise你把握不住?來看看異步轉同步改造看看Promise吧總結

看看Promise吧

var data = new Promise(function(resolve, reject){
    asynFunction(function(error, result){
          if(error){
               reject(error);
          }else{
               resolve(result);
          }
    });
});

async getData(){
    var result = await data();
    console.log('result is:', result);
}
      

就兩段代碼,第一段是構造Promise對象;第二段為調用Promise對象處理異步請求。

先說Promise。data是一個Promise對象,它内部調用了一個異步函數asynFunction(這個函數可以是查庫,請求其他網站接口,消息隊列擷取資料等),但是它能夠保證兩個結果:

處理異步請求成功的時候調用resolve函數,傳遞result對象;

出錯的時候調用reject函數,傳遞error對象。

就這麼簡單。

第二段代碼就是一個聲明了異步響應的函數,雖然内部調用的Promise對象,又套娃調用了異步函數,但是await文法糖,能夠保證result變量指派這一行同步執行,也就是getData會等待result指派這一行異步調用執行完畢,再執行console.log。

這裡稍微有點繞了,請讀者停下來,好好思考整理一下。

簡單來說就是,在async聲明的異步函數中,同步等待Promise内部執行結果,就是,同步等待異步請求。

這樣,異步轉同步,很神奇吧!

像查詢資料庫這樣的異步操作就能夠在API中被等待,并響應給使用者了,下面來代碼了。

好,用koa接口得怎麼做呢?需要在資料庫調用端怎麼調整呢?

下面是runSql的簡化實作代碼,我們要改造它

const runSql = function(sql, callback){
    console.log('will run sql:', sql);
    //這裡是異步調用的,但是不是Promise實作,沒有辦法使用asyn,await來實作同步等待異步請求。
    db.query(sql, function(error, result, fields){
        try{
            if(error) throw error;
            console.log('query result:', result); 
        }finally{
            if(callback) callback(result, fields);
        }
    });
}
      

先看koa怎麼搞?

這個好辦, 下面是一個簡單的router(路由web API)。

我們看到在router.get 第二個參數加了async關鍵字修飾的方法。

根據ES6,我們知道async函數内可以使用await強制等待異步Promise函數的相應結果。

很明顯,上面的runSql函數傳回不是一個Promise對象的,是以這裡先調用一個未實作的函數:prunSql。

router.get('/asynQuery', async function(ctx, next){
    //參考上面Promise使用,聲明同步等待一個傳回Promise對象的函數。
    var data = await prunSql('SELECT * FROM COMPANY;');
    ctx.body = JSON.stringify(data)
});

      

這是一個新的接口, 跟其他接口不一樣的就是,它處理請求的方法是用async聲明的異步函數。

我們需要改造runSql函數

const prunSql = function(sql){
    console.log('will run sql:', sql);
    return new Promise(function(resolve, reject){
        console.log('asyn run sql: ', sql);
        db.query(sql, function(error, result, fields){
            if(error){
                console.log('[%s] asyn error:', error);
                reject(error);
            }else{
                console.log('asyn result:', result);
                resolve(result);
            }
        });
    });
};
      

這就是改造好的prunSql,這裡用了resolve函數把資料庫查詢結果傳遞出去。

這樣在使用Promise的then可以擷取到異步查詢結果。

或者在一個異步響應的方法内await Promise對象,等待查詢傳回值。

看看使用Promise處理後的效果:

Promise你把握不住?來看看異步轉同步改造看看Promise吧總結

問題至此,迎刃而解!

具體用什麼架構做接口不重要,了解整個思想才是重點。

貼一下代碼, 下面的資料庫查詢庫(db-lite.js),讀者可自行更換成其他異步查詢的庫,進行改造,不必過多糾結一定要使用本文的runSql函數。

const koa = require('Koa');                                                 
const app = new koa()

var router = require('koa-router')();

var db = require('./db-lite');

router.get('/', function(ctx, next){ctx.body='Hello, geeklevin!'});

router.get('/query', function(ctx,next){
    db.runSql("SELECT * FROM COMPANY; ", function(result,fields){
        console.log('查詢結果:', result);
        ctx.body = JSON.stringify(result);
        console.log('設定響應body成功!');
    });
    console.log('processing');
});

router.get('/asynQuery', async function(ctx, next){
    var data = await db.prunSql('SELECT * FROM COMPANY;');
    ctx.body = JSON.stringify(data)
});

app.use(router.routes());
const PORT = 8081
console.log('start app at port %s', PORT)
app.listen(PORT);
      

db-lite.js 代碼:

const mysql =  require('mysql');

const db =  mysql.createConnection({
    host: "localhost",
    port: 3306,
    user: "root",
    password: "12345678"
});

const logDbStat = function(){
    console.log("db state %s and threadID %s", db.state, db.threadId);
    // console.log("db detail:", db);
}

logDbStat();

console.log('start to connect mysql');
db.connect(function(err){
    if(err){
        console.log('fail to connect db',err.stack);
        throw err;
    }
    logDbStat();
});

const close = function(){
    db.destroy();
    console.log('db disconnected');
    logDbStat();
};

var counter = 0;
const runSql = function(sql, callback){
    counter++;
    var cv = counter;
    console.log('run sql[%s] :[%s]',cv, sql);
    db.query(sql, function(error, result, fields){
        try{
            if(error) throw error;
        }finally{
            if(callback) callback(result, fields);
        }
    });
}

const prunSql = function(sql){
    console.log('will sql:', sql);
    return new Promise(function(resolve, reject){
        counter++;
        var cv = counter;
        console.log('asyn run sql[%s] :[%s]', cv, sql);
        db.query(sql, function(error, result, fields){
            if(error){
                console.log('[%s] asyn error:', cv, error);
                reject(error);
            }else{
                // console.log('[%s] asyn result:', cv, result);
                resolve(result);
            }
        });
    });
};

const myDb = 'demo20210330';

runSql("USE " + myDb);

module.exports.runSql = runSql;
module.exports.prunSql = prunSql;
      

總結

Promise結合async和await結合能夠産生神奇的效果,也是很多新手過不去的坎,希望讀者都能夠熟練運用。

上面展示代碼,讀者可以自行思考,改造工作中使用的異步請求轉化為同步調用。

篇幅有限,當然這本質上還是異步執行的,希望讀者自行鞭策,思考了解。