由使用request-promise-native想到的非同步處理方法
問題場景
因為js語言的特性,使用node開發程式的時候經常會遇到非同步處理的問題。對於之前專長App開發的我來說,會糾結node中實現客戶端API請求的“最佳實踐”。下面以OAuth2.0為場景,需要處理的流程:
- 獲取access token
- 使用獲取到的token,發起API請求
- 處理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的時候,需要知道:
- await不能單獨使用,其所在的上下文之前必須有async
- 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,因此可以鏈式呼叫。