Делал я тут небольшой проект на чистом JS и в ходе оного потребовалось работать с Rest API. Ну не ручками ведь XMLHttpRequest дёргать, решил я, наверняка есть бесчисленное количество готовых решений для такой простой задачи?..


Как можно догадаться по КДПВ, я несколько ошибался; впрочем, обо всём по порядку. Но если вкратце — получился вот такой симпатичный велосипедик, с которым запросы к Rest API получаются, как и обещано в заголовке, компактными и красивыми.


image


Кандидаты


Итак, мне был нужен джаваскриптовский клиент для Rest API. Гугл выдал чуток библиотек — 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.ru","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 лицензии.

Спасибо за внимание.

Поделиться с друзьями
-->

Комментарии (57)


  1. Delphinum
    31.05.2016 15:28
    +2

    Гляньте в сторону BackboneJS REST API клиента, результат получится более структурированным. Ваше решение походит на JQuery лапшу.


    1. Amareis
      31.05.2016 15:42

      Можно поподробней в той части, что про лапшу?


      1. Delphinum
        31.05.2016 15:57
        -1

        Мир JS потихоньку отказывается от конструкций вида:

        object.method("param", "param").method2("param").method3("param")...
        

        и на то есть свои причины. В частности несколько подобных строк, идущих друг за другом ухудшают читабельность, а сам подход тяжело тестировать (если, конечно, это не фассадные методы к более простому интерфейсу, но я сомневаюсь что это так).

        Такой подход далек от кошерного ООП и больше походит на процедурный стиль программирования, где все действия выполняются в виде одного длинного алгоритма. BackboneJS же позволяет разделить логику обращения к REST API на небольшие компоненты (объекты модели), которые проще тестировать и расширять.

        Другими словами предлагаемое мной решение позволит вам использовать объектную модель для работы с REST API, вы же предлагаете использовать некую точку входа. Это как сравнивать Data Mapper и Table Gateway, понимаете разницу?


        1. hanovruslan
          31.05.2016 16:06
          +9

          Раскройте пожалуйста термин кошерного ООП ?


          1. Delphinum
            31.05.2016 16:17

            Кошерное ООП это в первую очередь простая объектная модель, разработанная с применением модульного тестирования и самодокументирующимся интерфейсом, достаточно гибкая для расширения, но не слишком, дабы не усложнять решение.

            Предложенное автором решение ближе к инфраструктуре, нежели к модели (я это прекрасно понимаю), но используемый интерфейс (столь нелюбимые мной цепочки вызовов) меня сильно смущает, ибо усложняет читабельность. Мне пришлось перечитать несколько раз примеры автора, чтоб хоть немного въехать в то, что они делают.

            А теперь сравните с реализацией на BackboneJS:

            var bookList = new BookList;
            bookList.fetch();
            
            var book = bookList.findWhere({name: 'Use Backbone'});
            book.set('name', 'No use Backbone';
            book.save();
            
            var newBook = new Book({name: 'Use XHR'});
            bookList.add(newBook);
            


            1. Amareis
              31.05.2016 16:53

              //предполагается что name используется в качестве id
              //иначе что в этом примере, что в вашем, надо разруливать
              //возможные совпадения названий у двух и более книг
              var bookRes = api.books('Use another-rest-client');
              
              var book = bookRes.get();
              book.name = 'Always use another-rest-client!';
              bookRes.put(book);
              
              var newBook = api.books.post({name: 'Don't use pure XHR'});

              Как видно, по коду значимых отличий нет. Разумеется, есть отличия, вызванные другой парадигмой (у вас есть неявный локальный кеш), но объём кода в случае another-rest-client абсолютно такой же. Что до понятности… Ну, вообще, если бы в моём случае были бы шоткаты для методов на полученном объекте, код был бы ещё понятней, хотя он и сейчас вроде не выглядит чем-то странным.


              var book = api.books('Use another-rest-client').get();
              book.name = 'Always use another-rest-client!';
              book.$.put();  //вот такой магии пока нет и не факт что будет, хотя я думал о ней и до этой публикации


              1. Delphinum
                31.05.2016 17:18
                +3

                Вот я вам и советую не изобретать свое, а посмотреть в сторону BackboneJS. Поверьте, это очень небольшая библиотека, вы с ней ознакомитесь в течении нескольких часов и не пожалеете, раз уж пришли к тем же решениям.

                Из возможных отличий:
                Как вы решаете проблему преобразования нестандартного ответа сервера в стандартные объекты?
                Предполагается ли расширение коллекций и объектов логикой или это только простые хранилища данных (по аналогии с Data Gateway)?


                1. Amareis
                  31.05.2016 17:27

                  Что подразумевается под нестандартным ответом? Если это какой-то кастомный Content-Type, можно зарегистрировать свой encoder/decoder, в readme это описано. Если ничего подобного нет, возвращается простая строка, с которой можно делать всё что душе угодно. Если сервер отвечает со статусом не 200, 201 или 204, Promise реджектится с инстансом xhr, с которым, опять же, можно делать что угодно.


                  Хранилищ данных там никаких нет — если только вы не подразумеваете под ними возвращаемые из промиз объекты. Последние — это просто преобразованные в удобоваримый вид ответы сервера, которые библиотекой вообще никак не запоминаются и не используются.


                  1. Delphinum
                    31.05.2016 17:31

                    Что подразумевается под нестандартным ответом?

                    На пример ответ приходит не ввиде массива сущностей, а в таком виде:
                    {
                      collection: [
                        {сущность},
                        ...
                      ]
                    }
                    


                    Последние — это просто преобразованные в удобоваримый вид ответы сервера, которые библиотекой вообще никак не запоминаются и не используются

                    Пример: что делать, если коллекция bookList должна включать нестандартный метод?


                    1. Amareis
                      31.05.2016 17:36

                      Ну, например:


                      var books = (await api.books.get()).collection;

                      Какой ещё нестандартный метод? Вроде бы в Rest API никаких нестандартных методов быть не может.


                      1. Delphinum
                        31.05.2016 17:42

                        Тобишь парсинг ответа в ответсвенность библиотеки не входит.

                        Какой ещё нестандартный метод? Вроде бы в Rest API никаких нестандартных методов быть не может

                        В REST API то не может, но может быть на уровне JS, на пример такой:
                        var bookList = new BookList;
                        
                        var book = bookList.findFromAuthor('Name');
                        


                        1. Amareis
                          31.05.2016 17:54
                          +2

                          На самом деле это хорошая идея — дополнительный обработчик для определённых ресурсов. Впрочем, над этим ещё нужно поразмыслить. Любые методы и дополнительные обработчики — это уже ответственность data layer, а не простого клиента. И да, я понимаю к чему вы клоните — используй BackboneJS, Amareis, велосипеды не нужны (несложно было догадаться, учитывая что вы сказали это прямым текстом несколько комментов назад :)! Но, как я уже говорил, это разные уровни архитектуры и разные цели. Я написал простую обёртку для XHR, предназначенную для упрощения кода взаимодействия с определённым типом API. Конкретно для этого тот же бэкбон будет явным оверкиллом — в конце концов, я мог и jQuery для того плагина подтащить, не так уж это и страшно.


                          1. Delphinum
                            31.05.2016 17:56

                            Мы друг друга поняли )


        1. Amareis
          31.05.2016 16:08

          Объектная модель — это, безусловно, хорошо. Проблема в том, что это уже скорее тот самый data layer, нежели простой клиент. Я могу (и я подумываю об этом) сделать another-data-layer, который вполне может использовать another-rest-client в качестве бэкенда, но это будет уже совсем другая история.
          А ещё, цепочка вызовов здесь оправдана тем, что она, по сути, является отражением итогового URL.


          api.games(15).players(2).pet(4).get()

          Превратится в:


          GET http://example.com/api/v1/games/15players/2/pet/4

          Отображение практически один в один, но при этом абсолютно не замусорено лишними символами и позволяет легко спрятать используемую несколько раз часть цепочки под алиас:


          let me = api.games(15).players(2);
          me.pets(4).get();
          me.friends(17).delete();

          В моих задачах этого пока более чем достаточно. Если будет мало — действительно, можно и свой data layer над этим надстроить, но цель этой библиотеки совсем другая.


          1. Delphinum
            31.05.2016 16:19

            Если будет мало — действительно, можно и свой data layer над этим надстроить

            Вот я вам и предлагаю BackboneJS ) Конечно вы можете отказаться, ведь дело ваше.


          1. boblenin
            31.05.2016 17:24
            +1

            Подскажите пожалуйста. Для чего требуется превращать URL в цепочку вызовов? Я правда не понимаю.


            1. Amareis
              31.05.2016 17:33

              Во-первых, это красиво… :)
              Собственно, мне просто не нравится конструировать строки руками. Это выглядит хуже. Впрочем...


              api.res('games/15/players/2/pet/4').get();

              Такой код вполне легитимен, хотя библиотека на него и не рассчитана (внутри созданные ресурсы кешируются, так что если использовать такой стиль, можно однажды обнаружить что вкладка малость раздулась в памяти). Если вам это не нравится, скорее всего вам нужен rest, в котором как раз такой подход.


              1. boblenin
                31.05.2016 18:54

                Понятно. Ну если вам нравится, то в общем и ладно. На вкус и цвет…

                Мне вот ваш вариант со строчкой нравится больше, хотя бы потому, что я ее могу скопировать в REST client и выполнить не заморачиваясь перекодированием из часть().часть().часть() нотации в часть/часть/часть нотацию. Да и если ваш код придется читать кому-то, кто его первый раз видит — будет гораздо проще понять куда же вы ходите за данными, опять же поиск по коду можно делать.

                Конструирование строк руками вы так и так делаете только в одном случае вы результат можете использовать как минимум двумя способами, а в другом только одним (переиспользование не только кода, но и артифактов кода). Ну и наверняка реализация будет проще без заворачивания строк в объекты.

                Кстати если захочется выполнять какую-нибудь ODATA, то заварачивание выражений в объекты — это верный путь к костылям.


                1. Amareis
                  31.05.2016 19:21

                  Ну, положим, просто так скопировать её у вас не получится потому что вообще-то она будет выглядеть так:


                  api.res('games/' + game.id + '/players/' + player.id +'/pet/' + pet.id);
                  //ну или так, что сути особо не меняет
                  api.res(`games/${game.id}/players/${player.id}/pet/${pet.id}`);

                  Все эти кавычки и прочие скобочки-плюсики — лишний визуальный мусор, который затрудняет чтение и понимание кода. Добавьте сюда усложнение алиасов — в них надо будет хранить строки и вручную прибавлять их в начало формируемой строки. Собственно, всё это ручное конструирование мне и не понравилось настолько, что я отказался от использования rest и взял restful.js.


                  1. boblenin
                    31.05.2016 19:34

                    Согласен. Реальный код скорее всего будет параметризован

                    * api.games(game.id).players(player.id).pet(pet.id).get()
                    * api.res(`games/${game.id}/players/${player.id}/pet/${pet.id}`)

                    но может ведь быть еще

                    * api.res(`games(${game.id})/players(${player.id})/pet(${pet.id})`)

                    что на сервере получается из предыдущего примера с помощью нехитрых настроек, так же как видите в случае строки — легким и непринужденным изменением строки, но вот когда у вас все завернуто в объекты, то вы уже вынуждены делать серьезный рефакторинг.


                    1. Amareis
                      31.05.2016 19:36

                      Но это уже будет не Rest API, так что использовать для него Rest client будет неразумно ;)


                      1. boblenin
                        31.05.2016 19:44

                        Почему это будет не REST API? ODATA ( http://www.odata.org/ ) использует именно такой способ обращения к элементам коллекции и очень даже себе REST. Разве где-то есть требования к REST API формировать адреса так как вы написали выше? Если да — можете ссылку кинуть я бы с удовольствием прочел.


                        1. Amareis
                          31.05.2016 20:00

                          Честно говоря, я немного растерян. Ладно что я слышу об ODATA впервые, но все, абсолютно все API которые я когда-либо видел, делал или щупал, использовали традиционный путь со слешами! Это настолько базовая вещь что я действительно удивлён что она не эксклюзивна… Хотя постойте, ODATA делали в майкрософт?.. Хорошо, теперь я удивлён чуть поменьше.
                          Но всё равно считаю что это тот случай, когда спасуют все универсальные решения — тот же Backbone, например (хотя вот rest… Rest с этим справится, да); так что я готов с чистой совестью признать что тут мой велосипед не проедет. Хотя вы, конечно, всё ещё можете запихнуть сырую строку в метод res, но я уже предупредил что это нецелевое использование, которое может вызывать undefined behavior, рак мозга и случайные чёрные дыры на орбите Земли.
                          Используйте на ваш страх и риск.


                          1. Delphinum
                            31.05.2016 20:04

                            Но всё равно считаю что это тот случай, когда спасуют все универсальные решения — тот же Backbone, например

                            Backbone по умолчанию сам формирует url для запроса данных из API, но если вам его подход не нравится, переопределить алгоритм формирования url в Backbone (если я не путаю) не составляет особого труда.


                          1. boblenin
                            31.05.2016 20:09
                            +2

                            Ну что ж. В любом случае спасибо за ваши ответы и за то, что сделали свою библиотеку, поделились с людьми и рассказали о ней. Успехов вам в нелегком этом труде :)


            1. Delphinum
              31.05.2016 17:34

              Если API реализовано соответствующим образом, такой подход реализует Query Object. Насколько актуально такое API, хз.


              1. boblenin
                31.05.2016 19:01

                Ну собственно чем многие ORM-ы и занимаются.

                (кстати перевод статьи промптом сумашедший — куда проще понять, что автор хотел сказать тут http://martinfowler.com/eaaCatalog/queryObject.html )


                1. Delphinum
                  31.05.2016 19:04

                  Не совсем, ORM-ы только преобразуют реляционную структуру в объектную и обратно. QO нужен для формирования запросов в объектно нотации. Решение автора в таком случае может послужить удобным механизмом для формирования запросов к API на основании данных формы фильтра (на пример).


                  1. boblenin
                    31.05.2016 19:23

                    Не понмаю в чем удобство. Может быть я что-то упускаю. Я вижу два варианта

                    • get('/games/15/players/2/pet/4')…
                    • api.games(15).players(2).pet(4).get()…


                    В одном случае реализация не зависит от того как автор API структурировал свои адреса (например /games(15)/ или /games/15/ или вообще /games?gamesId=15), в другом зависит и как решать неоднозначности вроде /games/(15) не понятно.

                    Если считать скорость набора текста — во втором варианте писать больше надо.
                    Если размер приложения, опять же надо больше кода для реализации.
                    Если с точки зрения эффективности — опять же приложению надо делать обратную трансформацию из дерева выражений в строку.
                    Если с точки зрения эффективности разработки — строчку с URL легче и найти в коде и прочитать.


                    1. Amareis
                      31.05.2016 19:32

                      Выше я вам уже ответил — вспомните что id ресурсов вовсе даже не хардкодятся (по большей части).


                      get(`/games/${game.id}/players/${player.id}/pet/${pet.id}`)
                      api.games(game.id).players(player.id).pet(pet.id).get()

                      Писать получается меньше, читать — проще (лично мне, по-крайней мере). Насчёт эффективности вы правы — каждый такой вызов клонирует всё поддерево ресурсов, но я не думаю что эту задержку можно заметить в реальной жизни. Ну а насчёт размера приложения… Не знаю даже, вряд ли он будет у вас значимо меньше. У меня в коде вот это поведение занимает 70 строк, ещё около сотни — независимые функции, которые так или иначе придётся реализовывать.
                      Я не просто так сделал акцент на "client that makes your code lesser and more beautiful than without it" в описании репозитория.


                      1. boblenin
                        31.05.2016 20:06

                        Безусловно. Сейчас ваше приложение 70 строк, пока вы реализовали (как вы сами пишете в статье) часть того функционала, который может понадобиться. Я предполагаю, что своей реализацией вы покрыли 80% своих потребностей, и как всегда по эмпирическому принципу 80-20, оставшиеся 20% потребуют 80% усилий, а значит ваша библиотека если вы ее продолжите развивать — станет толще и тяжелее.


                    1. Delphinum
                      31.05.2016 19:32

                      Если вам нужно собирать адрес для запроса динамически, то без Object Query это будет сделать сложно, ибо он инкапсулирует сборку строки в себе.

                      На пример вот так:

                      var url = api.from('site.ru/api/games')
                      if(filter.gameId !== undefined)
                        url.games(filter.gameId);
                      if(filter.authorId !== undefined)
                        url.author(filter.authorId);
                      


                      1. boblenin
                        31.05.2016 19:39

                        Согласен, что возможны ситуации когда действительно нужно динамически строить запросы. Правда на моей памяти это всегда было плохой идеей (приложения клиенты чаще всего выполняют одни и те же запросы, что в свою очередь дает возможность серверу оптимизировать их выполнение), но все-таки еще раз скажу что пожалуй соглашусь с вами — если хотим делать динамические запросы — то некоторое удобство от библиотеки, которая динамически строит запросы будут.

                        Вопрос нужна ли эта библиотека в остальных случаях, когда запросы очень даже статичны?


  1. svekl
    31.05.2016 15:47
    +1

    Выглядит очень приятно в сравнении с другими rest клиентами, которыми приходилось пользоваться. Спасибо.


  1. alexmay23
    31.05.2016 15:50

    В чем проблема юзать XHR?


    1. Amareis
      31.05.2016 15:51

      Как вот такой код будет выглядеть на XHR? (Кстати, внутри библиотеки именно он и используется).

      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. alexmay23
        31.05.2016 16:02

        Я могу прорезюмировать лишь, что ее оправдано использовать если количество кода написаного в помощью XHR больше, в противном случае ради оптимизации лучше XHR; Чем меньше тем лучше, короче.


        1. Delphinum
          31.05.2016 16:05

          Оптимизации чего? Все подобные решения это всего лишь обертки над XHR, а они не сильно ресурсоемки.


        1. Amareis
          31.05.2016 16:14
          +1

          Проблема в том, что каждый раз создавать и инициализировать XHR вручную — глупо. Наверняка вы напишете для этого некую обёртку. И тут, сюрприз-сюрприз, вы обнаружите что сами изобрели ещё один Rest API client :)
          В общем-то another-rest-client именно так и появился на свет, я это и в статье описал.


        1. svekl
          31.05.2016 16:20

          Используя XHR Вы ведь всё равно обернёте вызов в код, который вернёт Вам промис, а это и будет практически эта самая библиотека, так зачем писать по-новой один и тот же велосипед в каждом проекте?


          1. alexmay23
            31.05.2016 16:51

            К примеру есть Лендинг на котором дергается всего 2-3 запроса, и ради этого использовать библиотеку? Серьезно?


            1. Amareis
              31.05.2016 16:56
              +1

              Я, например, дольше буду вспоминать весь workflow XHR, чем клиенты суммарно времени потеряют, подтягивая несчастные 4 килобайта, которые весит минифицированный another-rest-client :)


            1. svekl
              31.05.2016 17:02

              Почему нет? Зачем мне снова писать точно такую же обёртку в каждом проекте?


            1. Delphinum
              31.05.2016 17:22

              Не встречал еще лендинг с REST API


              1. alexmay23
                31.05.2016 20:04

                ну подтягиваются к примеру отзывы пользователей о сервисе, реальные, и еще что то. Вполне нормально.


                1. Delphinum
                  31.05.2016 20:06

                  Ну если у вас в API один метод вида site.ru/api/comments то конечно подключать зависимость для вызова было бы странно. Не столько из за производительности, сколько из здравого смысла, ибо подключение зависимости у вас займет больше времени, чем написания велосипеда на XHR.


            1. bromzh
              31.05.2016 18:07
              +3

              В таком случае можно использовать window.fetch и его полифил. Самый жирный плюс такого решения — это стандарт. И когда браузеры станут нормально его поддерживать, можно безболезненно убрать полифил.


  1. gearbox
    31.05.2016 18:24
    +2

    Пусть здесь полежит: https://github.com/swagger-api/swagger-js


    1. Amareis
      31.05.2016 18:25

      Кстати, я подумывал о том, чтобы прикрутить парсинг OpenAPI json'ов, это достаточно очевидная идея.


  1. devlato
    31.05.2016 18:46
    -1

    Пока лично для меня ценность библиотеки как-то сомнительна, поскольку ни удобства использования, ни какой-то замечательной функциональности не вижу, но в любом случае библиотека, может, и неплоха


  1. Fedcomp
    31.05.2016 20:30
    +1

    Оно конечно не Rest, но для тех кто любит «Pure» Fetch api


  1. dunmaksim
    31.05.2016 21:43

    Dojo Toolkit: AMD, красиво, работает, ничего лишнего (не считая кучи мелких файлов, подгружаемых при инициализации).


    Описываем хранилище моделей:


    define([
        "dojo/store/JsonRest"
    ], function(JsonRest){
        return new JsonRest({
            target: "/api/users/",
            _getTarget: function(id){
                if (typeof id !== "undefined") {
                    return this.target + id + "/"; // В Django принято ставить / в конце адреса
                }
                return this.target;
            }
        });
    });

    Где-то в коде:


    require([
        "dojo",
        "stores/users"
    ], function(
        dojo,
        users
    ) {
         // Получить список всех пользователей
        users.query({
            // Пустой объект - получить всё, а можно же и с параметрами
        });
    
        // Получить запись с id = 12
        users.get(12).then(
            // Тут всё стандартно - обработчики promise
            // Однако, dojo не терпит пустоты, поэтому на обработчик ошибки ставьте
            // dojo.noop, если писать свой лениво
        );
    
        // Обновление с перезаписью (можно и PATCH сделать, но это, мягко говоря, не для новичков)
        users.put(userModel).then(function(updatedUser){
            console.log(updatedUser);
        }, dojo.noop);
    
        // Удаление записи
        users.delete(5).then();
    });

    Официальная документация.


    Если кому-то интересно, специально для Django есть несколько строк кода, позволяющих в каждый запрос вставлять CSRF-токены.


  1. napa3um
    01.06.2016 11:42
    +1

    «Настоящий» REST не предполагает конструирование клиентом параметрических урлов вообще, кроме одного, указывающего на точку входа API. Все остальные урлы предоставляются сервером клиенту в самих ресурсах, это называется HATEOAS. (Понятно, что библиотека разрабатывалась для общения с «неправильными» сервисами, добавил комментарий лишь для академической полноты.)


  1. IvanPanfilov
    01.06.2016 17:05
    -1

    просто оставлю это здесь

    https://learn.javascript.ru/fetch

    'use strict';

    fetch('/article/fetch/user.json')
    .then(function(response) {
    alert(response.headers.get('Content-Type')); // application/json; charset=utf-8
    alert(response.status); // 200

    return response.json();
    })
    .then(function(user) {
    alert(user.name); // iliakan
    })
    .catch( alert );

    нативно, просто и быстро, без дурацких велосипедов


    1. bubuq
      07.06.2016 12:05
      +1

      fetch это скорее замена велосипедов вокруг XMLHttpRequest, чем реализация REST.


  1. murzilka
    01.06.2016 18:07

    >В самом начале своего существования restful.js была очень похожа на первые версии another-rest-client. Потом, видимо, скатилась в энтерпрайзщину.

    А почему не был исследован этот вопрос? Почему ограничились предположением о том, что какой-то дядя «энтерпрайз» пришёл и сделал из хорошей и удобной библиотеки плохую и неудобную?

    Может, библиотека развивалась от неудобного к удобному? Может, если пилить велосипед дальше, будет пройден ровно тот же самый путь?


  1. vitalets
    03.06.2016 14:20

    Это хорошо, что вы сделали все сами. Но странно, что не рассмотрели такие варианты:
    https://github.com/visionmedia/superagent
    https://github.com/mzabriskie/axios