В Neovim добавили vim.async для модернизации асинхронной архитектуры

Открытый консольный текстовый редактор Neovim, форк от легендарного Vim, взял от своего родителя самое лучшее, внедрив при этом собственные новшества. В отличие от специфичного VimScript, редактор использует более быстрый и распространенный язык Lua. Преимуществ у этого решения много, но сегодня мы поговорим об одном из главных — асинхронности.

Зачем она вообще нужна в редакторе? Все просто: Neovim, как и большинство текстовых редакторов, работает в одном главном потоке. Если пытаться, например, выполнять сложный поиск по проекту с помощью синхронного кода, весь интерфейс редактора зависнет до окончания операции. Чтобы этого избежать, Neovim использует event loop на базе библиотеки Libuv (на которой, кстати, построен Node.js).

До появления модуля vim.async разработчикам приходилось использовать подходы, которые могли быть довольно неэффективными, а иногда и вовсе проблематичными. Например, при использовании vim.loop и vim.uv можно было попросить NeoVim асинхронно прочитать файл, а после завершения передать результат callback-функции.

vim.uv.fs_open("input.txt", "r", 438, function(open_error, fd) if open_error then return print(open_error) end vim.uv.fs_read(fd, 4096, 0, function(read_error, data) if read_error then print(read_error) else print(data) end vim.uv.fs_close(fd) end)end)

Если продолжать усложнять цепочку таких вызовов, можно попасть в так называемый callback hell, нечитаемую «пирамиду смерти». В этом случае код можно было сделать более читаемым с помощью отдельных функций или оберток.

Корутины тоже помогали решать эту задачу: они позволяли приостанавливать выполнение функции до определенного момента. Благодаря этому асинхронный код можно было писать более линейно, но для этой цели использовались сторонние решения, включая plenary.async, async.nvim и собственные абстракции. Такой подход уменьшал вложенность, однако приводил к фрагментации, так как разные плагины могли использовать разные соглашения: о создании задач, обработке ошибок и так далее. Отсутствие единой модели осложняло взаимодействие и делало эффективное управление жизненным циклом задач более трудоемким.

Создайте веб‑приложение и получите бонусы на его деплой в облако

В новом бесплатном курсе по JavaScript.

Подробнее →

Что изменилось с vim.async

vim.async добавляет в стандартную библиотеку встроенную модель структурированной конкурентности, выступая заменой собственных корутин-оберток. В рамках новой модели асинхронные подпрограммы выполняются внутри задач, создаваемых с помощью vim.async.run(). Когда задача ожидает события или операции ввода-вывода c использованием vim.async.await(), NeoVim останавливает выполнение фрейма и возвращает управление event loop. Это гарантирует, что синхронные операции редактора и пользовательский ввод продолжатся без прерываний.

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

На данный момент представлены следующие примитивы, с помощью которых можно управлять асинхронностью:

  • vim.async.semaphore() — ограничивает количество одновременных операций. Это может быть полезным, например, при обработке большого количества файлов или сетевых запросов.

  • vim.async.timeout() — задает временное ограничение для операции или групп операций, отменяя их выполнение при превышении заданного времени.

  • vim.async.iter() — позволяет получать результаты выполняющихся задач в порядке их фактического завершения, а не в порядке запуска.

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

Выводы

Реакция сообщества на эти изменения была в основном положительной. Единая асинхронная абстракция стала пластырем для такой болевой точки экосистемы как несовместимость плагинов, вызванная конкурирующими сторонними библиотеками. Конечно, дискуссии по решению тоже есть, например вызывают вопросы нюансы распространения ошибок и, в частности, само взаимодействие vim.async.await() с Libuv, однако изменение явно решает проблемы многих разработчиков, а это самое главное.

Источник: habr.com

0 0 голоса
Рейтинг новости
1
0
Подписаться
Уведомить о
0 комментариев