Pull to refresh

Comments 21

На $mol это было бы как-то так, если дословно переводить:

const getA = sync( ()=> {
  const a = sync( api ).getA()
  return a
} )

const getB = sync( params => {
  const b = sync( api ).getB( params )
  return b
} )

const event = task( () => {
  const a = getA()
  const b = getB(a)
  setState( b )
} )

И так, если нормально делать:

class App extends Object {

  @mem api() {
    return new Api
  }
  
  @act getA() {
    const a = this.api().getA()
    return a
  }

  @act getB( params ) {
    const b = this.api().getB( params )
    return b
  }

  @mem state() {
    const a = this.getA()
    const b = this.getB(a)
    return b
  }
  
}

Неделю назад написал эту штуку для своей библиотеки @cmmn/cell, но еще не добавил в либу
Выглядит так:

const a = new CellQuery<ResultA>('/api/a');
const b = new CellQuery<ResultB>(() => a.isFetching ? undefined : `/api/a/${a.data.id}`);

и в реакте

const {isFetching, data} = useCell(b);
return isFetching ? <Skeleton/> : <div>{data}</div>;

CellQuery чекает зависимости и вызывает fetch если что-то изменилось. Если уже идет запрос, он отменяется.

Хорошая статья. Хорошо, когда в проекте это реализовано изначально и все пишут с таким подходом на их стеке технологий проекта.

Плохо, когда этого совсем нет)

Повторюсь, важное отличие реализации отмены в Reatom от rxjs и
redux-saga является в использовании нативного AbortController, который
уже является стандартом, используется в браузерах и node.js, а также
множества других библиотек

Все же он используется не напрямую, а внутри withAbort() абстракции. Что тоже "специфический АПИ". А завязка внутри на AbortController - наоборот делает реализацию менее универсальной. Например, в rxjs - unsubscribe можно написать для любой асинхронной операции, даже если она не поддерживает AbortController. К примеру, XmlHttpRequest.

Это хороший комментарий! Примеры в статье очень простые и мы вызываем reatomAsync из reatomAsync, но ctx и контроллер в нем просачивается на любую глубину вызовов через все атомы, экшены и хуки и может быть использован через onCtxAbort, например.

Rxjs придумали в 1875.

Люди до 1875 - как отменять цепочки асинхронных событий???

Уважаемые фронтовики (или передовики, как правильно?)!
Как задовик, я понимаю ваши проблемы и даже немного сочувствую. Но — не разделяю.
Например, я в примере (точнее, на диаграмме) вижу нарушение принципа "дураку полработы не показывают": ну вот зачем после getA надо показывать промежуточные результаты пользователю (которого чисто для пользы дела следует считать дураком)? Лучше было бы дождаться getB, собрать всю полученную инфорацию в кучу и сразу обновить отображаемые данные. Ведь в коде примера, на самом деле, оно так и сделано, если я правильно понял смысл заклинания setState. Версионирование при таком подходе работает на ура — версию достаточно проверить один раз, перед обновлением отображаемых пользователю данных. В примере, насколько я понял это стоит сделать в setState. Благо, вы там на фронте можете себе позволить дождаться конца каждой цепочки, не экономя на запросах к серверу — задовики как-нибудь справятся, да ;-).
Во-вторых, я рад, что до фронта наконец-то добрался шаблон скоординированной отмены (который AbortController, в C# это — CancellationToken и все что вокруг него). Вот проблемы с прокидыванием его я как раз понимаю — у нас так же мучаться приходится. И решение примерно то же самое — контекст. И даже для прокидывания контекста ничего особо изобретать не нужно — для этого есть объекты и ключевое слово this в их методах. У вас на фронте тоже так же можно, только чуть побольше "бойлерплейта" надо — например, писать "function" вместо "=>" (ну, и список параметров переставить по месту). Как по-моему, то лишние 6 символов на функцию того стоят. Может, попробуете? В комменте nin-jin примерно показано, как это делается (подсказка: его любимый $mol для этого, в общем-то, не нужен).
А вот чего, по-моему, делать не стоит — так это использовать для хранения контекста глобальную переменую — даже которая на уровне модуля. Ибо это — костыль жуткий: что вы делать-то будете, когда вам потребуется параллельно выполнять две цепочки? Или это — чисто для примера? Так для примера я бы блок не поленился нарисовать — у вас же модуль и там всегда "use strict", так что всего два символа — и любой приверженец методологии разработки SO-Driven Development будет в безопасности (относительной) ;-)
А что по поводу Reatom, то я к лишней зависмости всегда отношусь с подозрением (я не люблю зависимости, хотя, допускаю, что я просто не умею их готовить). Потому пофантазирую: нельзя ли то же самое сделать через Proxy в чисто конкретных местах?


PS Надеюсь, то что я тут говорю тут про задачи (Task/promise) а не про async/await это никого не смущает? Если вдруг смущает — могу пояснить.
PPS Немного побуду Шишковым: "to track" переводится с английского (в данном контексте) как "отслеживать". Так что, можно вместо изобретения своих словоуродцев использовать его. Впрочем, лично для меня это не страшно — читать русские тексты по IT методом обратного перевода я привык со времен ЕС ЭВМ — но я за других беспокоюсь.
PPS Если чо не то сказал — извиняйте: ну, не фронтовик я (и не передовик).

А как это делается без $mol?

Что-то типа такого (основываюсь на вашем примере из второго листинга, а так как JS — не мой родной язык, то если что не так — подправьте)


{
  class App {
    constructor(controller_name) {
       this.сontrollerName = controller_name;
       this.controller = new AbortController();
    }

    async event(getA, getB) {
       if(this.controller) controller.abort(controllerName);
       this.controller = new AbortController();
       const a = await this.getA();
       const b = await this.GetB(a);
       // controller.throwIfAborted() добавить по вкусу
       return b;
    }
  }

  async function getA() {
     let a;
     //...do smth
    this.controller.throwIfAborted();
     //...do smth more
    //при необходимости повторить предыдущие две строчки до готовности
    return a
  }

  async function getB(a) {
     let b;
     //...do smth with a
    this.controller.throwIfAborted();
     //...do smth more with a
    //при необходимости повторить предыдущие две строчки до готовности
    return b
  }
}

У вас даже близко не то же самое: функции другого цвета, куча бойлерплейта, ручное прокидывание аборт контроллера и прерывание функций, ещё и апи потерялось.

Дык вопрос-то был, как это сделать без этого вашего $mol в принципе, а не как это сделать красиво. Кароче, вы мне тут напишите — код работать будет вообще, в принципе? Если да, то мне для иллюстрации достаточно.
PS Api — это DI-котейнер, или что? Если DI-котейнер, то Api не нужен потому что я написал PoC: нужные функции передаются напрямую, как параметры. Впихнуть DI-котейнер несложно, только вот он число сущностей умножит, а я старался упростить.


И да, getA и getB просто не читайте: они тут чисто для иллюстрации, кужа скоординированную отмену впихивать, дабы не гадал никто.

api - фасад к серверу, который собственно и выполняет запросы.

Это — свой объект для каждой цепочки запросов? Тогда вот вам и готовый контекст, куда можно AbortController засунуть. Ну и, чтобы сделать такой, какой-то особый фреймворк не нужен — достаточно создать объект и прописать в нем нужные методы. Вы согласны?

Ну, это ваш выбор. Не смею навязывать свой.
Хотя… Если прикладные программы всегда получают его через new Api, как у вас в примере, то можно совершенно прозрачно поменять функцию Api так, чтобы она возвращала новый объект, унаследованный от основного объекта Api, но со своим собственным AbortController.

Как получают не важно, api - один объект, который реализует много разных методов.

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

Привет, спасибо за такой подробный комментарий, но вы многое упускаете :)

  • "зачем показывать промежуточные результаты?"

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

  • "версию достаточно проверить один раз"

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

  • "контекст"

    Это общее слово и тут нужно понимать про какой контекст мы говорим, в каком контексте :) Есть контекст модели - инстанса. Есть асинхронный контекст вызова - процедуры. Абсолютно разные вещи.

    Для асинхронного контекста сама переменная "context" - просто токен, данные по которому меняются и для каждой асинхронной цепочки свои.

  • "лишней зависмости"

    В жаваскрипте плохой STD и вообще нет фреймворков, вы либо пишите вагоны велосипедов, либо пишите говнокод без хороших практик. Отсутствие библиотек - очень тревожный звоночек. У библиотек документация и тесты будут точно лучше внутренних реализаций, у нас такая культура во фронте. Кстати, бандлсайз Reatom очень маленький: 2кб core и еще 2-4кб на async и остальные вспомогательные либы. Сам React весит 44.

Привет, спасибо за такой подробный комментарий, но вы многое упускаете :)
«Нельзя объять необъятное» (Козьма Прутков). Я всего лишь демонстрирую взгляд с другой стороны.
Мы всегда стараемся показать данные как можно быстрее в хорошем интерфейсе.
А эти данные в таком неполном виде точно для пользователя какую-то ценность имеют? Если да, и если данные отображаются в нескольких частях (типа основной записи и детализации или наоборот, каких-то суммарных значение), то ваш варинт — обновление интерфейса несколько раз в процессе запроса — имеет смысл, и однократной проверкой номера версии тогда не обойтись. Но в примере — в коде, а не на диаграмме- обновление интерфейса было одно, и я на это обратил внимание.
Тем не менее, идея с версией обновления мне лично не кажется особо хорошей, шаблон скоординированной отмены мне нравится больше. Может, потому что привык — он в C# уже больше 10 лет существует.
встречаются более сложные примеры с несколькими отдельными визуально, но связанными логически интерфейсами,

Мое мнение — делать обновление таких частей методом(ами) одного объекта, который и будет контекстом для всей цепочки. Благо JS позволяет много чего лишнего, чего лишены разработчики на языках со статической типизацией ;-), в данном случае — заимствование методов из других объектов.
нужно понимать про какой контекст мы говорим, в каком контексте

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

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

Пока нативного AsyncContext нет - все так :)

Хотя и с ним проблем достаточно останется. Если писать только на нативке, без библиотек, у вас либо будет чрезмерно упрощенный и не надежный код, либо собственные библиотеки. Ну либо задачи решаете очень простые, конечно.

Хотя я вот сколько мелких одностраничных лендингов / приложений не писал, все равно для хорошего UX нужно либо написать кучу кода, либо тащить тяжелые библиотеки. Мой опыт. Поэтому Reatom и пишу, пытаюсь решить основные юзкейсы за бандлсайз, который не страшно тащить даже на леднос.

Sign up to leave a comment.