Pull to refresh
69
0
Алексей Гурьянов @Guria

UX Developer, Cerebral JS Core team member

Суть инъекции в том что, в тексте который подавался как "данные" для анализа модель нашла "исполняемый" код и выполнила его.

Не спорю. Первое читаемее второго. Только они не эквивалентны.
В function-tree это будет выглядеть:


[
  foo,
  bar,
  baz
]

Вот ещё свежий доклад
https://youtu.be/SfWR3dKnFIo?t=18m10s
Внимание на слайд:

обычно это выглядит так:


function getB({ input: { smthImportantForMe } }){
}

Да, возможно, контекст не самое красивое решение, но он работает.
См. также:
https://youtu.be/vpc80c5iC6k?list=PLglJM3BYAMPH2zuz1nbKHQyeawE4SN0Cd&t=643

// работа с ошибками через оператор throw, предусмотренный в языке специально для таких случаев

Я не хочу exception. Неполучение данных для меня не исключение, а норма в условиях http. Использование try catch в качестве условия ветвления логики до добра не доводит.
Цитата из статьи:


Часто потоки мыслятся как: "сделай это или бросай всё, если произойдёт ошибка". Но не в случае с веб-приложениями. Есть много причин, почему вы решите пойти вниз по различным путям выполнения.



// ключевое слово yield наглядно показывает, что выполнение прерывается и мы ждем завершения асинхронной операции

Только зачем мне это при чтении потока?


exec([
  foo,
  bar // наглядно показывает, что bar выполнится только после foo. И не важно синхронные они или нет.
])

exec([
  foo,
  [ // параллельное выполнение bar и baz после foo
    bar,
    baz
  ],
  theEnd // выполнится только после bar и baz. Аналог Promise.all([bar, baz]).then(theEnd)
]) // exec кстати тоже возвращает Промис который резолвится объединённой полезной нагрузкой
// Полезно для тестирования, но не рекомендуется для композиции, во избежание дробления. Дерево является полным и самодостаточным описанием потока. Нет необходимости оборачивать его в try catch.



// нельзя просто вернуть результат, каждый раз необходимо придумывать бессмысленное название поля для каждой конкретной функции. Ненужные поля продолжают копиться внутри context грудой хлама и гулять по всем функциям.

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




// необходимость вручную вызывать path.error => возможность пропустить необработанную ошибку

Использование try catch для отлова ошибки не возбраняется. Но обработать её надо там же где она возникла, не полагаясь на вызывающую сторону, если только вы не хотите восстанавливать поток по крупицам в своей голове.


function getA ({someService, path}) {
  try {
    return path.success({ importantThing: someService.dangerousGetSmth() })
  } catch (e) {
    return path.error({ importantThingError: e.toString() })
  }
}

На самом деле лучше если обработка ошибки будет обработана в сервисе


function getA ({someService, path}) {
  return someService.saveGetSmth().then(
    entity => path.success({ importantThing: entity }),
    error => path.error({ importantThingError: error }),
  )
}

А ещё ошибки бывают разные:


exec([
  getEntity, {
    sucess: [processEntity],
    notFound: [showError('entityNotFound'), showCreateEntityDialog], // 404
    unuathorized: [showError('unuathorized'), showRequestPermissionsDialog]
    serviceFailed: [showError('serviceFailed'), showTryLaterNotification] // 503
  }
])



// непонятно, синхронная функция или асинхронная, невозможно делегировать исполнение другому генератору через yield*, только через функцию-костыль

Синхронность функции — деталь реализации. Из описания дерева и так явно следует порядок исполнения.




// не предусмотрено слияние веток исполнения; при возможности использовать всплытие мне не пришлось бы писать два раза одно и то же

Я не знаю, что вам помешало вынести ветку обработки ошибки в отдельную функцию или даже цепочку. Дерево описывается в js синтаксе.



// chains/processErrors.js

export default [
  logErrorToConsole,
  showNotificationToUser,
  logErrorToGoogleAnalytics
]

// chains/getSmth.js
import getA from '../actions/getA'
import getB from '../actions/getB'
import processErrors from './processErrors'

export default [
  getA, {
    success: [
      getB, {
        success: [({input}) => console.log(input.result)],
        error: [...processErrors, showRetryGetBDialog]
      }
    ],
    error: [...processErrors, showRetryGetADialog]
  }
] // и скобочки не танцуют :(
  1. Здесь, видимо, переводчик халтурит, я не говорил, что композицию нужно делать с помощью папок. Переводите нормально, или бросьте эту затею.

Предложите перевод для Вашей фразы "Создайте папку", более адекватный чем "Create a folder". А то я прямо в растерянности. :)
При этом к фразе был приложен контекст из оригинальной статьи, аналогичный процитированному вами куску перевода.


Если вам будет удобно, можете обратиться к автору напрямую в нашем discord-чате. Если нет, то продолжу переводить. Нам важен фидбек и я его передам.

Christian:


  1. Also Sinon was mentioned as a mocking tool. I can see the point, but personally I think it is better to mock arguments than global dependencies. Cause you do not need a mocking tool, you just mock the context as any argument to a normal function
  2. I do not agree that a folder with files gives the same readability as one file where you see the order of functions execution. Maybe a misunderstanding?
  3. With Elm/Redux-loop/Cyclejs you can not compose them in one place. The reason being that a state change is what returns a new side effect. Which means that for every side effect that causes a state change you have to return a new side effect. This can not be composed in one file as you define state changes one place and side effects an other place
  4. Yeah, this is totally true in general. But this context is created for each function tree. It is no more god-like than creating a class where the context is "this", in my opinion
  5. As I understand GOTO labels can jump wherever in the code. Function tree is stricter in that regard, it is not about "jumping", it is about choosing "the next possible step"
  6. I would argue that using mocking tools to mock global deps is not better than mocking an argument to a function. Mostly because most tools we mock returns promises, which means you do not need to mock the tool itself, you just mock the promise
  7. Decoupling is exactly what reduces readability. You need somewhere to compose it all together so you do not have to build up the mental image of how it runs. If the tree is endless I agree, but I have never met an endless tree I have met a lot of decoupled code that has been hard to compose a mental image of though


Have to say thanks for great comments if you have a chance But I think maybe one thing that goes missing a bit here is that you can achieve all the traits of what function-tree tries to give you with discipline. But that is very hard to achieve in code in general and especially in teams. Function-tree just helps you achieve these attributes that produces readable code. And to boot it is able to visualize your code in a way that is not possible with plain JS, using its debugger.

But yeah, great comments

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

Christian:


it requires a very high level of discipline to uphold that dividing of logic which can be difficult to do in teams. With function-tree you are forced to divide your logic, in a good way. Also "built-in JS" features does not ensure testability, function-tree does because you put all "globals" on the context. And last but not least… you can not have a debugger that understands "built-in JS features"
К слову, Кристиан утверждает: «The example was typical for what I have seen in projects. Maybe we should change that example if it is not valid. I can understand if people use like Saga or something similar, but a lot of people do not.»
Именно. А промисы резолвятся не синхронно, а в микротаске.
Мы знаем про redux-saga. Возможно стоило добавить его к сравнению в статье. Однако внимательный читатель встретит аргументы касательно методов используемых в саге. Хотелось бы более развёрнутого комментария.
Вы правы. Примеры в статье, к сожалению, немного гипертрофированы. В реальном приложении я бы обернул ajax запросы в сервис с понятными методами. Это позволит тестировать экшены предоставив мок сервис в контекст в тестовой среде.
Боевой же сервис (который и будет устанавливать нестандартнык заголовки) скорее надо тестировать с помощью интерсептеров.

По производительности, возможно, не самое лучшее решение. Однако обработка управления потока на клик пользователя врядли будет узким местом приложения.

А вот по опыту cerebral мы получаем множество положительных отзывов о итоговой читаемости. О том как легко становиться понимать приложение в целом. В итоге некоторое даже стали пытаться использовать cerebral в nodejs, например, для работы с firebase и т.п.
Большой action creator действительно спорная часть статьи. Кристиан пытался показать весь поток при наступлении определённого события. Обычно в redux так не принято. Но в «правильном» redux не будет такого единого места где можно увидеть весь поток со всеми возможными путями, его придётся восстанавливать в голове каждый раз возвращаясь к этому коду.
Если есть идеи как этот поток можно выразить лучше, пожалуйста дайте пример. Возможны мы просто не умеем готовить редакс :)
На этом примере происходит знакомство с принципами работы function-tree, а не способов последовательного запуска асинхронных функций.

К слову, ваш вариант не является полноценным эквивалентом, т.к. в случае с function-tree функция bar может быть синхронной и не возвращать промисов.
Актуален ли фикс сегодня? Стоит ли держать его в проекте по сей день?
Эта штука вообще не про докер, т.к. ядра линукса нету. Докер под виндой нынче нативный есть в публичной бете.
То что мне надо описано в разделе Module Augmentation.
Вот только проблема, что я не могу написать Scale.prototype.advancedMethod = /* insert implementation */; в t.sd файле
скомпилируется из ts в es6?

let { a, b, ...rest } = props

Тут надо аналог babel для ts исходника :)
Вот никак не могу понять как правильно написать d.ts для moment / jquery плагина, если в качестве менеджера тайпингов у меня typings.
Пол дня уже долблюсь. Среди уже опубликованных тайпингов подходящего примера не нашлось.

Information

Rating
Does not participate
Location
Самарская обл., Россия
Date of birth
Registered
Activity