由使用request-promise-native想到的非同步處理方法

scorpiozj發表於2019-02-16

由使用request-promise-native想到的非同步處理方法

問題場景

因為js語言的特性,使用node開發程式的時候經常會遇到非同步處理的問題。對於之前專長App開發的我來說,會糾結node中實現客戶端API請求的“最佳實踐”。下面以OAuth2.0為場景,需要處理的流程:

  1. 獲取access token
  2. 使用獲取到的token,發起API請求
  3. 處理API資料

處理過程

一開始,我們使用了閉包巢狀閉包的方式實現,形如:

request(options, (res, error)=>{
    //handle res and error
    request(options2, (res2, error2)=>{
        //handle res2 and error2
    })
})

 

我們可以允許函式的非同步執行,但大多數人在思考問題的時候,尤其在解決如上的場景時,還是希望能採用線性地處理方式。於是,我們使用request-promise-native,配合aync/await,類似:

1 (async ()=> {
2     let access = await requestpromise(authoptions).then((value)=>{
3         return value;
4     }).catch((error)=>{
5         return error;
6     });
7     console.log('access', access);
8 })();

 

使用async/await的時候,需要知道:

  1. await不能單獨使用,其所在的上下文之前必須有async
  2. await 作用的物件是Promise物件

可以猜想 request-promise-native 必定是對request進行了Promise化,從原始碼中可以看到(雖然我沒看懂,應該是使用了通用的方法來建立Promise):

// Exposing the Promise capabilities
var thenExposed = false;
for ( var i = 0; i < options.expose.length; i+=1 ) {
    var method = options.expose[i];
    plumbing[ method === 'promise' ? 'exposePromise' : 'exposePromiseMethod' ](
        options.request.Request.prototype,
        null,
        '_rp_promise',
        method
    );
    if (method === 'then') {
        thenExposed = true;
    }
}
if (!thenExposed) {
    throw new Error('Please expose "then"');
}

 

既然如此,我們可以構造Promise,交給await。下面就把request包裹成一個Promise:

 1 //token.js
 2 module.exports.getAccessToken =  async (options) => {
 3     return new Promise(function (resolve, reject) {
 4         request(options, function (error, res, body) {
 5           if (!error && res.statusCode == 200) {
 6             resolve(body);
 7           } else {
 8               if(error){
 9                   reject(error);
10               }else{
11                 reject(body);
12               }
13           }
14         });
15     });
16 };
17 //app.js
18 (async ()=> {
19     let access = await token.getAccessToken(authoptions).then((value)=>{
20         //handle value if requires
21         return value;
22     }).catch((error)=>{
23         return error;
24     });
25     console.log('access', access);
26     //use token to send the request
27 })();

 

API成功返回的結果我們往往需要按需處理,這一步放在then函式中進行。因為Promise呼叫then仍然是Promise,因此這裡鏈式呼叫的then和catch。
進一步地,我們嘗試使用內建模組 util 對函式進行promise化,形如:

//token.js
const request = require('request');
const {promisify} = require('util');
const requestPromise = promisify(request);
module.exports.getAccessToken =  async (options) => {
    return requestPromise(options);
};
//app.js
(async ()=> {
    let access = await token.getAccessToken(authoptions).then((value)=>{
        //handle value if requires
        return value;
    }).catch((error)=>{
        return error;
    });
    console.log('access', access);
    //use token to send the request
})();

 

說了這麼多,對我而言,目前最大的收穫就是理解了如何使用Promise/async/await,把非同步函式順序執行:把帶有閉包的函式包裹進Promise,然後使用async/await執行該Promise。

好了,以上是我解決此類問題的思路。我相信必然還有其他優雅的解決方式,甚至是最佳實踐。今天,藉此機會,拋磚引玉,希望大家能夠不吝賜教。

Promise 內容複習

最後,容我溫習一下Promise相關的內容,有片面的地方請大家指正。
Promise物件:

The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.

Promise有三種狀態: 初始狀態,執行成功,執行出錯。 then()表示promise執行後的進一步處理,它可以帶兩個callback引數:第一個用於promise成功執行後執行,第二個表示promise執行失敗後執行。catch()表示promise執行失敗後所執行的工作。catch()可以理解為語法糖,當then()的第二個callback引數省略的時候,意味著需要呼叫catch(因為未處理的失敗的promise在將來某個node版本會導致程式退出)。需要注意的是,then()/catch()方法也是返回Promise,因此可以鏈式呼叫。

參考

Promise-MDN web docs
用圖表和例項解釋 Await 和 Async

javascript 學習: async await

相關文章