Neovim Lua用のPromiseを実装した

2021/11/28に公開

NeovimのLua製プラグインで使えるPromiseを実装した。

https://github.com/notomo/promise.nvim

なぜ?

世にPromiseのLua実装は幾つかあるが、
Neovimで使う際に欲しい機能が少しずつ足りなかったので実装した。
以下でNeovimやLuaに特有な点を紹介する。

Neovimのevent-loopで非同期実行する

JavaScriptのPromise.thenは常に非同期で実行される。

Promise.resolve().then(function() {
  console.log(2);
});
console.log(1);

// 1
// 2

promise.nvimではこの非同期実行のためにvim.schedule()を使っている。
vim.schedule()に渡された関数はNeovimのmain event-loopで実行されるようにキューに入る。

vim.schedule(function()
  print(2)
end)
print(1)

-- 1
-- 2

Unhandled Rejectionを検知する

rejectされたpromiseがcatchされない場合、エラーを握りつぶされるとデバッグが困難になる。
JavaScriptの実行環境のように何かしら検知する手段が欲しい。

promise.nvimではpromiseがGCに回収される際にUnhandled Rejectionを判定するようにした。
Neovim LuaはLuaJIT(Lua 5.1 compatible)なのでtableについてはGC時に__gcが呼ばれない。
よってuserdata__gcを使うハックが必要になる。
以下に簡略化したコードを示す。

do
  -- promiseのstatus, handledが最終的に以下になったとする
  local promise = {status = "rejected", handled = false}
  -- 空のuserdataを作るハック http://lua-users.org/wiki/HiddenFeatures
  local userdata = newproxy(true)
  getmetatable(userdata).__gc = function()
    if promise.status == "rejected" and not promise.handled then
      print("unhandled rejection")
    end
  end
  -- https://www.lua.org/manual/5.1/manual.html#2.10.2
  -- promiseへの通常の参照が消えたらuserdataがGCの対象になる
  promise._userdata = setmetatable({[promise] = userdata}, {__mode = "k"})
end
collectgarbage() -- GCを明示的に動かす

-- unhandled rejection

多値を扱う

多値をresolve, rejectできるようにした。

local Promise = require("promise")
Promise.new(function(resolve)
  resolve(1, 2)
end):next(function(a, b)
  return a, b
end):next(function(a, b)
  print(a + b)
end)

-- 3

不要かなと迷いつつ、2値ぐらいは使いそうだったので実装した。
この機能がなくてもtableに入れてresolve, rejectして使う側で展開すれば足りる。
ただ、その変数に名前を付けるのが面倒な場面もあり、気楽さを優先してこうした。
Luaの書き味には合ってる気がする。

プラグインに埋め込む

今のところLua製プラグインの依存ライブラリを管理する仕組みはないので、
ユーザーに依存ライブラリを別途インストールしてもらうか、
プラグインに埋め込むしかない。

promise.nvimはライセンスをCC0にして気楽に扱えるようにしたので、
1ファイルを例えばlua/myplugin/promise.luaにコピペすれば以下のように使える。

local Promise = require("myplugin.promise")

(もちろん手動で管理するのは辛い)

感想

ちゃんとPromiseを学んだことがなかったので JavaScript Promiseの本 がとても参考になった 🙏

Discussion