Запити до Rest API з JavaScript компактно і красиво

я Робив тут невеликий проект на чистому JS і в ході цього потрібно працювати з Rest API. Ну не ручками адже XMLHttpRequest смикати, вирішив я, напевно, є незліченна кількість готових рішень для такої простої задачі?..
Як можна здогадатися по КДПВ, я трохи помилявся; втім, про все по порядку. Але якщо коротко — вийшов ось такий симпатичний велосипедик, з яким запити до Rest API виходять, як і обіцяно в заголовку, компактними і красивими.
image
Кандидати
Отже, мені був потрібен джаваскриптовский клієнт для Rest API. Google видав трохи бібліотек — restful.js, rest, amygdala. Насправді, була ще ось така бібліотечка, але це плагін до jQuery. jQuery в проекті не використовується і тягнути його якось не хотілося, але, почасти, пропонований бібліотекою синтаксис мені сподобався і це ще спливе згодом.
Amygdala відпала відразу — ні Promise, а значить немає і async/await. Ще й кордону функціональності у неї дивні, amygdala швидше претендує на щось на зразок недо-data-layer; про відсутність сбилженной версії і зайві залежно я тактовно промовчу.
Залишилося два кандидати — restful.js і rest.
rest пропонує якесь мінімальне ядро і дає широкі можливості по його кастомізації за допомогою так званих "перехоплювачів" — interceptors в оригіналі. Не знаю наскільки це круто — перспектива будувати повні урли і вказувати метод руками при кожному запиті мене зовсім не приваблювала, перехоплювачів для модифікації цієї поведінки не спостерігалося, так і документація захоплення не викликала. Я перейшов до останнього варіанту — restful.js.
A pure JS client for interacting with server-side RESTful resources. Think Restangular without Angular.
Взагалі-то я віддаю перевагу Ember, але яка різниця? Головне що б використовувати зручно було!
const articleCollection = api.all('articles'); // http://api.example.com/articles

// http://api.example.com/articles/1
api.one('articles', 1).get().then((response) => {
const articleEntity = response.body();

// if the server response was { id: 1, title: 'test', body: 'hello' }
const article = articleEntity.data();
article.title; // returns `test`
article.body; // returns `hello`
// You can also edit it
article.title = 'test2';
// Finally you can easily update it or delete it
articleEntity.save(); // will perform a PUT request
articleEntity.delete(); // will perform a DELETE request
}, (response) => {
// The reponse code is not >= 200 and < 400
throw new Error('Invalid response');
});

Це приклад з документації. Виглядає непогано в порівнянні з конкурентами, так і документація досить виразна… Варіантів-то все одно більше не спостерігається. Беремо? Беремо.
Невеликий ліричний відступ
Є така концепція "розумного умовчання", яка передбачає, якщо переказувати своїми словами, що рішення якоїсь проблеми заточене, умовно, під 90% юзкейсов, а в інших 10% потрібно явно сказати що є особливі обставини.
Тривіальний приклад цієї концепції — параметри за умовчанням в більшості мов програмування.
Куди менш тривіальний приклад (який, тим не менш, просто узагальненням попереднього) — оптимізація будь-якого інтерфейсу виходячи з очікуваної поведінки користувача, будь то командний інтерфейс, графічний, програмний або взагалі фізичний: рубильники там, кнопочки та інші хардварные крутилки-вертелки.
Загальна стратегія цих оптимізацій однакова — 90% користувачів для досягнення своїх цілей повинні робити так мало дій, як це взагалі можливо. Не буде великим перебільшенням сказати, що це загальна формула прогресу — винахід все більш і більш простих, з точки зору кількості необхідних рухів, інтерфейсів для вирішення тих же завдань.
І, загалом-то, ці самі розумні умовчання — один з головних способів спрощення.
Навіщо я граю в Капітана Очевидність, розтікаючись миссю по древу? Тому що черговий велосипед — це просто черговий велосипед, а от велосипед з підведеною під нього філософською базою вже не просто черговий велосипед, а навіть може здалеку і на мотоцикл змахувати!
Кінець невеликого ліричного відступу.
restful.js
Отже, restful.js. Використовував я його вкрай недовго — і пари днів не минуло, як я зрозумів що:
  1. Кожен раз явно викликати all() — не круто.
    let games = api.all('games'); //<-- не круто
    games.get();
    //...
    games.post();

    Ресурси API — це те, що лежить в основі всього фронтенда, так якого ж вони щоразу створюються вручну? Це якраз ті речі, які повинні бути вшиті у тканину проекту.
    api.games.get(); api.games.post()
    — виглядає куди краще (так, це як раз спливло вплив синтаксису тієї бібліотечки на jQuery). Втім, це ще можна було обійти, ми ж динамічні бояри:
    api.games = api.all('games');

  2. Ручне розгортання відповіді і entity — взагалі не круто!
    let games = (await api.games.get()).body().data(); //<-- that sucks

    Очі б мої не бачили, пальці б не писали, але доводилося. Тут би ось ті перехоплювачі з rest придалися б, там функціональність розгортання сирого відповіді в об'єкт як раз реалізована. В restful.js теж є перехоплювачі, але тут вони скромніше, не те.
  3. Ох, а ще — вищенаведена рядок коду аж двічі неправильна. По-перше, не get, а getAll, ми ж колекцію запитуємо, а не окремий інстанси. По-друге, data() у колекції не визначено — вийде
    Uncaught (in promise) TypeError: _temp.body(...).data is not a function
    . Будь ласка, використовуй forEach, визначений як метод у entity, яку повертає body() у response, який повертає getAll().
загалом, класичний приклад незручного інтерфейсу. Я не знаю, вплив це ангуляра, який начебто славиться своєю академічністю, або орієнтація швидше на буття фреймворком, ніж бібліотекою, але пропонований restful.js інтерфейс мені сильно не сподобався. Насправді, за підсумком він сподобався мені, напевно, навіть менше ніж інтерфейс конкурентів — мабуть, ефект зловісної долини позначається: близько до ідеалу, але не дотягла, а від любові до ненависті всього один велосипед.
Що ж я зробив?
Викинув restful.js і накидав два класи, які за 150 рядків коду робили в принципі те ж, що і restful.js за 6000 (шість тисяч, це не помилка).
Потім подумав, виклав на github, порефакторил, освоїв webpack (чудова штука!), mocha+chai, sinon і travis, виклав на npm і bower, написав документацію, запив приклад і в підсумку написав цю статтю, щоб полегшити життя тим, хто зіткнеться з такою ж проблемою.
На даний момент (червень 2016) там замало тестів, немає методів HEAD і OPTIONS, складно отримати сирої відповідь і занадто мало бейджів в README (всього один, що за ганьбу!..).
Втім, це все легко виправити. Головне що another-rest-client надає зрозумілий і простий інтерфейс, з яким мені подобається працювати; сподіваюся, що і не тільки мені.
Трохи коду
Використання github API:
var api = new RestClient('https://api.github.com');
api.res({repos: 'releases'});

api.repos('Amareis/another-rest-client').releases('latest').get().then(function(release){
console.log(release);
document.write('Latest release of another-rest-client:<br>');
document.write('Published at:' + release.published_at + '<br>');
document.write('Tag:' + release.tag_name + '<br>');
});

Вкладені ресурси? Запросто:
var api = new RestClient('http://example.com/api/v1');
api.res({ //or it gets object and returns object where resource is available by name
dogs:[
'toys',
'friends'],
cats: 0,
humans:
'posts'
});
/* last string is equal to:
api.res('dogs').res(['toys', 'friends']);
api.res('cats');
api.res('humans').res('posts'); */

api.dogs(1337).toys.get(); //GET http://example.com/api/v1/dogs/1337/toys
api.dogs(1337).friends(2).delete(); //DELETE http://example.com/api/v1/dogs/1337/friends/2

//POST http://example.com/api/v1/humans/me/posts, body="{"site":"habrahabr.ua","nick":"Amareis"}"
api.humans('me').posts.post({site: 'habrahabr.ru', nick: 'Amareis'});

З async/await код виходить куди веселіше:
var me = api.humans('me');
var i = await me.get();
console.log(i); //just object, i.e. {id: 1, name: 'Amareis', profession: 'programmer'}
var post = await me.posts.post({site: 'habrahabr.ru', nick: i.name})
console.log(post); //object

Випадкові цікаві факти
  1. Чому така назва? Ну, спочатку він був просто rest-client. Але ця назва (а також ще кілька схожих) включена в npm, так і унікальність так собі, так що я додав трохи самоіронії і він став another-rest-client.
  2. самому початку свого існування restful.js була дуже схожа на перші версії another-rest-client. Потім, мабуть, скотилася в энтерпрайзщину.
  3. В коді another-rest-client всього два коментаря (і я обурений тим, що їх дуже багато) і обидва вони містять прокльони в бік Javascript, який не дозволяє зробити код повністю красивим.
  4. Я так і не зрозумів чим WTFPL відрізняється від ліцензії MIT.
Дякую за увагу.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.