如何用 Node.js 編寫一個 API 客戶端

RaleighTolm 8年前發布 | 38K 次閱讀 API Node.js Node.js 開發

 

說幾句無關主題的話

盡管這幾年來 Node.js 已經得到越來越多的關注,連市場賣菜的老太婆都能分別得出哪個是寫 Node.js 的,哪個是寫 PHP 的。然而,終究是不能跟老大哥 Java 比的。我們在使用一些第三方服務時常常會 碰到一時半會還沒有官方的 Node.js SDK 的問題,所以能自己隨手擼一個剛好夠用的 API 客戶端來應急 成了必備技能。

說到這里,我忍不住要先吐槽一下:

前幾天在 CNodeJS 上看到一個帖子, 擁抱 ES6——阿里云 OSS 推出 JavaScript SDK 對其中的濫用 generator 還 洋洋自得 的行為有點不滿,之前也遇到過該廠的 SDK 強行返回 generator 而放棄使用,我想說我 已經忍了很久 了。

「我自己寫得爽,也希望把這種“爽”帶給用戶」-- 該 SDK 的維護者如是說

作為一個 SDK(尤其是官方出品的),應該使用最 common 的技術或規范來實現。比如在 Node.js 中的異步問題,應該使用傳統的 callback 或者 ES6 里面的 promise ,而不是使用 比較奇葩的 generator 來做。 generator 來做不妥的地方是:

  • generator 的出現不是為了解決異步問題
  • 使用 generator 是會傳染的,當你嘗試 yield 一下的時候,它要求你也必須在一個 generator function 內

當然,如果這是一個內部項目,使用各種花式姿勢都是沒問題的,只要定好規范就行。而如果這是要給別人 使用的東西,應該照顧其他人的感受。

所以我們要自己動手寫一個 SDK 還有另外一種情況就是 對官方的 SDK 并不滿意

好了,我吐槽完了。

運行環境

最近一年來,Node.js 相繼發布了 4.0、5.0、6.0(前幾天),7.0 也已經蓄勢待發,但目前來看 主流還是 4.x 版本 。Node.js 4.x 支持一部分的 ES6 語法,比如箭頭函數、 let 和 const 等,解決異步問題也可以直接使用 ES6 的 promise 。

如果沒有特殊情況,新寫的程序可以不用考慮在 0.12 或者更早的 0.10 上運行,如果以后確實需要在這些 版本上執行,可以借用 Babel 來編譯成 ES5 語法的程序。

API 接口將同時支持 callback 和 promise 兩種回調方式。 promise 直接使用 ES6 原生的 Promise 對象而不是使用 bluebird 模塊。盡管使用 bluebird 會有更多的功能和更好的性能, 但在這樣一個需要網絡 IO 的場景下,那么一點性能差別基本可以忽略不計,而作為一個極簡主義者,覺得 沒太大必要引入這么一個依賴庫。

功能設計

本文將以 CNodeJS 提供的 API 為例。CNodeJS 的 API 分兩種:

  • 公共接口,比如獲取主題列表和詳情等
  • 用戶接口,需要提供 accesstoken 參數來驗證用戶權限( accessToken 可以在個人設置界面中 得到)

程序的使用方法如下:

'use strict';

const client = new CNodeJS({
  token: 'xxxxxxx', // accessToken,可為空
});

// promise 方式調用
client.getTopics({page: 1})
  .then(list => console.log(list))
  .catch(err => console.error(err));

// callback 方式調用
client.getTopics({page: 1}, (err, list) => {
  if (err) {
    console.error(err);
  } else {
    console.log(list);
  }
});

初始化項目

1、首先新建項目目錄:

$ mkdir cnodejs_api_client
$ cd cnodejs_api_client
$ git init

2、初始化 package.json :

$ npm init

3、新建文件 index.js :

'use strict';

const rawRequest = require('request');

class CNodeJS {

  constructor(options) {

    this.options = options = options || {};
    options.token = options.token || null;
    options.url = options.url || 'https://cnodejs.org/api/v1/';

  }

  baseParams(params) {

    params = Object.assign({}, params || {});
    if (this.options.token) {
      params.accesstoken = this.options.token;
    }

    return params;

  }

  request(method, path, params, callback) {
    return new Promise((resolve, reject) => {

      const opts = {
        method: method.toUpperCase(),
        url: this.options.url + path,
        json: true,
      };

      if (opts.method === 'GET' || opts.method === 'HEAD') {
        opts.qs = this.baseParams(params);
      } else {
        opts.body = this.baseParams(params);
      }

      rawRequest(opts, (err, res, body) => {

        if (err) return reject(err);

        if (body.success) {
          resolve(body);
        } else {
          reject(new Error(body.error_msg));
        }

      });

    });
  }

}

module.exports = CNodeJS;

說明:

  • 使用 request 模塊來發送 HTTP 請求,需要執行命令來安裝該模塊: npm install request --save
  • 我們實現了一個帶有 request 方法的 CNodeJS 類,可以通過該方法來發送任意 API 請求, 比如請求主題首頁是 request('GET', 'topics', {page: 1})
  • 如果初始化 CNodeJS 實例時傳入了 token ,則每次請求都會自動帶上 accesstoken 參數
  • 返回的結果 success=true 表示 API 請求成功,則直接回調該結果;如果失敗則 error_msg 表示出錯信息

4、新建測試文件 test.js :

'use strict';

const CNodeJS = require('./');
const client = new CNodeJS();

client.request('GET', 'topics', {page: 1})
  .then(ret => console.log(ret))
  .catch(err => console.error(err));

5、執行命令 node test.js 即可看到類似以下的結果:

{ success: true,
  data:
   [ { id: '572afb6b15c24e592c16e1e6',
       author_id: '504c28a2e2b845157708cb61',
       tab: 'share',
       content: '.......'
...

至此我們已經完成了一個 API 客戶端最基本的功能,接下來根據不同的 API 封裝一下 request 方法 即可。

支持 callback

前文已經提到, 「作為一個 SDK,應該使用最 common 的技術或規范來實現」 ,所以除了 promise 之外還需要提供 callback 的支持。

1、修改文件 index.js 中 request(method, path, params) { } 定義部分:

request(method, path, params, callback) {
  return new Promise((_resolve, _reject) => {

    const resolve = ret => {
      _resolve(ret);
      callback && callback(null, ret);
    };

    const reject = err => {
      _reject(err);
      callback && callback(err);
    };

    // 以下部分不變
    // ...
  });
}

說明:

  • 將 new Promise() 中的 resolve 和 reject 分別改名為 _resolve 和 _reject
  • 在函數開頭新建 resolve 和 reject ,其作用是調用原來的 _resolve 和 _reject ,同時 判斷如果有 callback 參數,則也調用該函數

2、將文件 test.js 中 client.request() 部分改為 callback 方式調用:

client.request('GET', 'topics', {page: 1}, (err, ret) => {
  if (err) {
    console.error(err);
  } else {
    console.log(ret);
  }
});

3、重新執行 node test.js 可以看到結果跟之前是一樣的。

通過簡單的修改我們就已經實現了同時支持 promise 和 callback 兩種異步回調方式。

封裝 API

前文我們實現的 request() 方法已經可以調用任意的 API 了,但是為了是方便,一般需要為每個 API 單獨封裝一個方法,比如:

  • getTopics() - 獲取主題首頁
  • getTopicDetail() - 獲取主題詳情
  • testToken() - 測試 token 是否正確

對于 getTopics() 可以這樣簡單地實現:

getTopics(params, callback) {
  return this.request('GET', 'topics', params, callback);
}

但其返回的結果是這樣結構的:

{ success: true,
  data: []
}

要取得結果還要讀取里面的 data ,針對這種情況我們可以改成這樣:

getTopics(params, callback) {
  return this.request('GET', 'topics', params, callback)
             .then(ret => Promise.resolve(ret.data));
}

getTopicDetail() 和 testToken() 可以這樣實現:

getTopicDetail(params, callback) {
  return this.request('GET', `topic/${params.id}`, params, callback)
             .then(ret => Promise.resolve(ret.data));
}

testToken(callback) {
  return this.request('POST', `accesstoken`, {}, callback);
}

對于其他的 API 也可以采用類似的方法一一實現。

由此看來編寫一個簡單的 API 客戶端也不是一件很難的事情,本文介紹的方法已經能適用大多數的情況了。 當然還有些問題是沒提到的,比如阿里云 OSS 這種 SDK 還要考慮 stream 上傳問題,還有斷點續傳。 對于安全性要求較高的 SDK 可能還需要做數據簽名等等。

在編寫本文的時候,通過閱讀 request 的 API 文檔我才發現原來可以通過 json=true 選項來讓 它自動解析返回的結果,這樣確實能少寫好幾行代碼了。

另外我還是忍不住再吐槽一下,CNodeJS 的 API 接口設計得并不一致,響應成功時并不是所有數據都放在 data 里面(比如 testToken() )。

發覺最近有點上火了 ^_^

 

來自: http://morning.work/page/2016-05/how-to-write-a-nodejs-api-client-package.html

 本文由用戶 RaleighTolm 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!