Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign upGitHub is where the world builds software
Millions of developers and companies build, ship, and maintain their software on GitHub — the largest and most advanced development platform in the world.
Review: Async.Promise #567
Conversation
| elseif has_callback && success | ||
| call s:_resolve(a:promise, Result) | ||
| elseif !success | ||
| call s:_reject(a:promise, Err) |
vim-jp-bot
Dec 24, 2017
[vimlint] reported by reviewdog 🐶
EVL104: variable may not be initialized on some execution path: l:Err
[vimlint] reported by reviewdog
EVL104: variable may not be initialized on some execution path: l:Err
rhysd
Dec 24, 2017
Author
Contributor
この警告は問題ないことを確認済みですが,出ないようにコード直したほうが良いでしょうか?
この警告は問題ないことを確認済みですが,出ないようにコード直したほうが良いでしょうか?
lambdalisue
Dec 25, 2017
Member
提案
lint なので強制力は無いですが、出ないほうが望ましいと思います。
特に Result と合わせる必要もないので Err の代わりに v:exception を直接使えばいいかなという気がします
提案
lint なので強制力は無いですが、出ないほうが望ましいと思います。
特に Result と合わせる必要もないので Err の代わりに v:exception を直接使えばいいかなという気がします
rhysd
Dec 26, 2017
Author
Contributor
ですね,そうしておきます.ありがとうございます.
ですね,そうしておきます.ありがとうございます.
rhysd
Dec 26, 2017
Author
Contributor
v:exception は catch した節の中でしか使えないのを忘れていました…
v:exception は catch した節の中でしか使えないのを忘れていました…
lambdalisue
Dec 26, 2017
Member
mkj... 間違った情報すみません。
mkj... 間違った情報すみません。
|
#526 からレビューしてくれそうな気配がある人を Reviewers にアサインしてみました.年末で忙しいところすみませんが,レビューいただけると助かります. |
|
|
|
とりあえず |
|
|
||
| " @vimlint(EVL103, 1, a:resolve) | ||
| " @vimlint(EVL103, 1, a:reject) | ||
| function! s:noop(resolve, reject) abort |
lambdalisue
Dec 25, 2017
Member
メモ
すでに covimerage 側で修正されているかもしれませんが gina.vim に covimerage を導入した際に空っぽの関数があるとパースエラーが出ました。gina.vim では関数内部にコメントを一行書くことで対処しました。念のため
メモ
すでに covimerage 側で修正されているかもしれませんが gina.vim に covimerage を導入した際に空っぽの関数があるとパースエラーが出ました。gina.vim では関数内部にコメントを一行書くことで対処しました。念のため
rhysd
Dec 26, 2017
Author
Contributor
coverage ちゃんと取れてるっぽいです💪
coverage ちゃんと取れてるっぽいです
| elseif has_callback && success | ||
| call s:_resolve(a:promise, Result) | ||
| elseif !success | ||
| call s:_reject(a:promise, Err) |
lambdalisue
Dec 25, 2017
Member
提案
lint なので強制力は無いですが、出ないほうが望ましいと思います。
特に Result と合わせる必要もないので Err の代わりに v:exception を直接使えばいいかなという気がします
提案
lint なので強制力は無いですが、出ないほうが望ましいと思います。
特に Result と合わせる必要もないので Err の代わりに v:exception を直接使えばいいかなという気がします
| " expression by **reference**. | ||
| call map( | ||
| \ copy(a:promises), | ||
| \ {i, p -> p.then({v -> wait_group.notify_done(i, v)}, a:reject)}, |
lambdalisue
Dec 25, 2017
Member
確認
map に lambda 式を使うと 1.5 倍ほど遅くなりますがよろしいでしょうか?
https://gist.github.com/lambdalisue/7cbaa0ece9496ec8052f5a86d33d8f17#file-test-vim
ただ最速の expr タイプはわかりにくくなるので all で想定する promises 数がそんなに多くないのであれば許容範囲だと思います。
確認
map に lambda 式を使うと 1.5 倍ほど遅くなりますがよろしいでしょうか?
https://gist.github.com/lambdalisue/7cbaa0ece9496ec8052f5a86d33d8f17#file-test-vim
ただ最速の expr タイプはわかりにくくなるので all で想定する promises 数がそんなに多くないのであれば許容範囲だと思います。
rhysd
Dec 26, 2017
Author
Contributor
知りませんでした… 文字列版も検討してみます.ここは all() の引数なので,そこまで要素数が多くなることを想定していませんが,それでも速いに越したことは無いですし.
知りませんでした… 文字列版も検討してみます.ここは all() の引数なので,そこまで要素数が多くなることを想定していませんが,それでも速いに越したことは無いですし.
| function! s:_race(promises, resolve, reject) abort | ||
| for p in a:promises | ||
| call p.then(a:resolve, a:reject) | ||
| endfor |
lambdalisue
Dec 25, 2017
Member
確認
ここも同様に map(..., 'expr') タイプの方が 1.5 倍ほど早いですがよろしいでしょうか?
https://gist.github.com/lambdalisue/7cbaa0ece9496ec8052f5a86d33d8f17#file-test-vim
確認
ここも同様に map(..., 'expr') タイプの方が 1.5 倍ほど早いですがよろしいでしょうか?
https://gist.github.com/lambdalisue/7cbaa0ece9496ec8052f5a86d33d8f17#file-test-vim
rhysd
Dec 26, 2017
Author
Contributor
ベンチマークを測った上で検討してみます.正直,.then のオーバーヘッドのほうが大きくてほぼ影響ないんじゃないかという気もしています.
ベンチマークを測った上で検討してみます.正直,.then のオーバーヘッドのほうが大きくてほぼ影響ないんじゃないかという気もしています.
lambdalisue
Dec 26, 2017
Member
上記のベンチマークは 100000 回のループなので無視しても良いと思っています。
そのため「確認」としました
上記のベンチマークは 100000 回のループなので無視しても良いと思っています。
そのため「確認」としました
| endif | ||
| return child | ||
| endfunction | ||
| let s:PROMISE.then = function('s:_promise_then') |
lambdalisue
Dec 25, 2017
Member
質問
辞書に対して直接関数定義を行わないで、一度スクリプト関数として定義してから function で funcref 化して辞書に代入しているのには何か理由があるのでしょうか?
質問
辞書に対して直接関数定義を行わないで、一度スクリプト関数として定義してから function で funcref 化して辞書に代入しているのには何か理由があるのでしょうか?
rhysd
Dec 26, 2017
Author
Contributor
ここは以前ツイッターで @haya14busa さんに指摘されたのですが,辞書関数だとエラーが出た時のコールバックの関数名が数字になってしまってデバッグが困難になるためです.
ここは以前ツイッターで @haya14busa さんに指摘されたのですが,辞書関数だとエラーが出た時のコールバックの関数名が数字になってしまってデバッグが困難になるためです.
lambdalisue
Dec 26, 2017
Member
なるほど納得。でもデバッグの事情の為にこういうハックするのは少しモニョりますね。今回のケースだと非同期なのでデバッグの簡潔さを重要視した方が良いですが‥‥
なるほど納得。でもデバッグの事情の為にこういうハックするのは少しモニョりますね。今回のケースだと非同期なのでデバッグの簡潔さを重要視した方が良いですが‥‥
rhysd
Dec 26, 2017
Author
Contributor
コードが若干複雑になってしまうのは確かにあると思います.
コードが若干複雑になってしまうのは確かにあると思います.
haya14busa
Dec 27, 2017
Member
実は vital core でもやってます
vital.vim/autoload/vital/vital.vim
Line 76
in
e553b10
書くの面倒だという以外はいいことしかないと思ってる
実は vital core でもやってます
vital.vim/autoload/vital/vital.vim
Line 76 in e553b10
書くの面倒だという以外はいいことしかないと思ってる
| " States of promise | ||
| let s:PENDING = 0 | ||
| let s:FULFILLED = 1 | ||
| let s:REJECTED = 2 |
lambdalisue
Dec 25, 2017
Member
提案
ECMA script では Promise.race()で Promise の状態取得が出来ますが Vim script は関数呼び出しのオーバーヘッドが大きいので上記の定数を s:_vital_created(module) を使ってモジュール定数として定義し s:PROMISE._state を公開アトリビュートとする(必要であれば lockvar も?)のはどうでしょうか?
" 使用感イメージ
let p1 = Promise.resolve()
let p2 = Promise.reject('foobar')
let p3 = Promise.new({ resolve -> execute('echo "Do nothing"') })
echo p1.state == Promise.FULFILLED
echo p2.state == Promise.REJECTED
echo p3.state == Promise.PENDING
提案
ECMA script では Promise.race()で Promise の状態取得が出来ますが Vim script は関数呼び出しのオーバーヘッドが大きいので上記の定数を s:_vital_created(module) を使ってモジュール定数として定義し s:PROMISE._state を公開アトリビュートとする(必要であれば lockvar も?)のはどうでしょうか?
" 使用感イメージ
let p1 = Promise.resolve()
let p2 = Promise.reject('foobar')
let p3 = Promise.new({ resolve -> execute('echo "Do nothing"') })
echo p1.state == Promise.FULFILLED
echo p2.state == Promise.REJECTED
echo p3.state == Promise.PENDING
rhysd
Dec 26, 2017
Author
Contributor
すみません,#526 に1文だけ書いてこの PR には書いていなかったのですが,この PR は ES6 の Promise の API の実装にとどめる形にさせてください.理由は,
- bluebird のような強力な API を実装することも考えられるため,今入れたい API を言い始めるとキリが無さそう
- まずは最小の状態で master にマージして実戦投入して,real world で本当に必要な API だけを足したい
ためです.PR の本文にも追記しておきます💪
なのでここからは一応オフトピですが,せっかく提案いただいたので僕の考えを書いておきます.
一般的に,非同期処理で実行の状態を見て処理を分岐するのはバッドプラクティスだと思います.例えば JavaScript には VM 上で他の非同期に走っているコンテキストの状態を取る手段はありませんし,Go ではあえてゴルーチン(coroutine)の状態や ID,今自分がどのゴルーチン(コンテキスト)で実行されているかを取る API を提供していません.
また,飽くまで僕自身の経験としてですが,Promise の状態を取りたいと思ったことが無いというのもあります.
ただ,bluebird のように Promise の 3rd party 実装では Promise の状態を同期的に introspection できるものもあるので,real world で本当に必要なユースケースが発生した時に入れることを考えて議論するのが良いかなと思います.
すみません,#526 に1文だけ書いてこの PR には書いていなかったのですが,この PR は ES6 の Promise の API の実装にとどめる形にさせてください.理由は,
- bluebird のような強力な API を実装することも考えられるため,今入れたい API を言い始めるとキリが無さそう
- まずは最小の状態で master にマージして実戦投入して,real world で本当に必要な API だけを足したい
ためです.PR の本文にも追記しておきます
なのでここからは一応オフトピですが,せっかく提案いただいたので僕の考えを書いておきます.
一般的に,非同期処理で実行の状態を見て処理を分岐するのはバッドプラクティスだと思います.例えば JavaScript には VM 上で他の非同期に走っているコンテキストの状態を取る手段はありませんし,Go ではあえてゴルーチン(coroutine)の状態や ID,今自分がどのゴルーチン(コンテキスト)で実行されているかを取る API を提供していません.
また,飽くまで僕自身の経験としてですが,Promise の状態を取りたいと思ったことが無いというのもあります.
ただ,bluebird のように Promise の 3rd party 実装では Promise の状態を同期的に introspection できるものもあるので,real world で本当に必要なユースケースが発生した時に入れることを考えて議論するのが良いかなと思います.
lambdalisue
Dec 26, 2017
Member
ご説明ありがとうございます。納得しかない
ご説明ありがとうございます。納得しかない
| call timer_start(0, function('s:_publish', [a:promise])) | ||
| endfunction | ||
|
|
||
| function! s:_resolve_one(index, value) dict abort |
lambdalisue
Dec 25, 2017
Member
提案
辞書関数ではなく wait_group インスタンスを第一引数に持つ通常関数にしませんか?以下理由
wait_group の定義位置と離れているので self が何を指しているのかパッと理解できない
- Vim script だと
. によるアトリビュート検索も結構遅かった気がするので partial で変数化して Notify(i, v) みたいに呼び出したほうが繰り返し時に早そう(パフォーマンス測定はしていません)
提案
辞書関数ではなく wait_group インスタンスを第一引数に持つ通常関数にしませんか?以下理由
wait_groupの定義位置と離れているのでselfが何を指しているのかパッと理解できない- Vim script だと
.によるアトリビュート検索も結構遅かった気がするのでpartialで変数化してNotify(i, v)みたいに呼び出したほうが繰り返し時に早そう(パフォーマンス測定はしていません)
rhysd
Dec 26, 2017
Author
Contributor
なるほど.特に 1. はその通りですし,ここで辞書関数にする必要は実際無さそうなので普通の関数にします.ありがとうございます.
なるほど.特に 1. はその通りですし,ここで辞書関数にする必要は実際無さそうなので普通の関数にします.ありがとうございます.
| endfunction | ||
|
|
||
| function! s:_race(promises, resolve, reject) abort | ||
| for p in a:promises |
tyru
Dec 26, 2017
Member
p は Funcref になり得るので l:P がいいと思います
p は Funcref になり得るので l:P がいいと思います
lambdalisue
Dec 26, 2017
Member
あれ p は s:PROMISE オブジェクトのコピー(インスタンス)な気がします。
あれ p は s:PROMISE オブジェクトのコピー(インスタンス)な気がします。
tyru
Dec 26, 2017
Member
あ、確かに。了解です。
あ、確かに。了解です。
| endfunction | ||
|
|
||
| function! s:_resolve(promise, ...) abort | ||
| let Result = a:0 > 0 ? a:1 : v:null |
tyru
Dec 26, 2017
Member
同名のグローバル関数が定義されていた場合にエラーにならないように l:Result がいいと思います
同名のグローバル関数が定義されていた場合にエラーにならないように l:Result がいいと思います
| let success = 1 | ||
| if has_callback | ||
| try | ||
| let Result = a:callback(a:result) |
tyru
Dec 26, 2017
Member
同名のグローバル関数が定義されていた場合にエラーにならないように l:Result がいいと思います
同名のグローバル関数が定義されていた場合にエラーにならないように l:Result がいいと思います
rhysd
Dec 26, 2017
Author
Contributor
同名のグローバル関数があってもローカル変数が優先されてエラーにならないようにみえます.7.4 だとそうなるのでしょうか?
同名のグローバル関数があってもローカル変数が優先されてエラーにならないようにみえます.7.4 だとそうなるのでしょうか?
rhysd
Dec 26, 2017
Author
Contributor
7.4.729 でも試してみましたが結果は上のものと変わりませんでした
7.4.729 でも試してみましたが結果は上のものと変わりませんでした
| try | ||
| let Result = a:callback(a:result) | ||
| catch | ||
| let Err = v:exception |
tyru
Dec 26, 2017
Member
同名のグローバル関数が定義されていた場合にエラーにならないように l:Err がいいと思います
同名のグローバル関数が定義されていた場合にエラーにならないように l:Err がいいと思います
rhysd
Dec 26, 2017
Author
Contributor
(コメント書いた diff が outdated で閉じちゃったのでこちらにも)
同名のグローバル関数があってもローカル変数が優先されてエラーにならないようにみえます.7.4.729 までは同じ挙動のようなのですが,もっと古いものだとそうなったりするのでしょうか?
(コメント書いた diff が outdated で閉じちゃったのでこちらにも)
同名のグローバル関数があってもローカル変数が優先されてエラーにならないようにみえます.7.4.729 までは同じ挙動のようなのですが,もっと古いものだとそうなったりするのでしょうか?
haya14busa
Dec 27, 2017
Member
v:exeption は関数ではないので関係ないのでは? Result は関数になりうりそうなので l: つけておいてもよさそう。
あと関連して Err が funcref にならないなら大文字にする必要はないとおもいます. 逆に funcref なら大文字かつ l: はつけるべき。
v:exeption は関数ではないので関係ないのでは? Result は関数になりうりそうなので l: つけておいてもよさそう。
あと関連して Err が funcref にならないなら大文字にする必要はないとおもいます. 逆に funcref なら大文字かつ l: はつけるべき。
rhysd
Dec 27, 2017
Author
Contributor
@tyru なるほど…ありがとうございます.そういうことでしたか.
@haya14busa 確かに例外として投げられるのは文字列なので Err のほうは err で大丈夫そうですね.誤解生みそうなので修正します
@tyru なるほど…ありがとうございます.そういうことでしたか.
@haya14busa 確かに例外として投げられるのは文字列なので Err のほうは err で大丈夫そうですね.誤解生みそうなので修正します
| let parent = self | ||
| let state = parent._state | ||
| let child = s:new(s:NOOP) | ||
| let Res = get(a:000, 0, v:null) |
tyru
Dec 26, 2017
Member
同名のグローバル関数が定義されていた場合にエラーにならないように l:Res がいいと思います
同名のグローバル関数が定義されていた場合にエラーにならないように l:Res がいいと思います
| \ Promise.new({resolve -> timer_start(100, {-> resolve('second')})}), | ||
| \]) | ||
| \.then({v -> execute('echo ' . v)}) | ||
| \.catch({e -> execute('echo ' . e)}) |
tyru
Dec 26, 2017
Member
execute('echo e') か execute('echo ' . string(e)) ?
execute('echo e') か execute('echo ' . string(e)) ?
| \ Promise.new({resolve -> timer_start(50, {-> execute('throw "ERROR!"')})}), | ||
| \ Promise.new({resolve -> timer_start(100, {-> resolve('second')})}), | ||
| \]) | ||
| \.then({v -> execute('echo ' . v)}) |
tyru
Dec 26, 2017
Member
execute('echo v') か execute('echo ' . string(v)) ?
execute('echo v') か execute('echo ' . string(v)) ?
| \ Promise.new({resolve -> timer_start(50, {-> resolve('first')})}), | ||
| \ Promise.new({resolve -> timer_start(100, {-> resolve('second')})}), | ||
| \]) | ||
| \.then({v -> execute('echo ' . v)}) |
tyru
Dec 26, 2017
Member
execute('echo v') か execute('echo ' . string(v)) ?
execute('echo v') か execute('echo ' . string(v)) ?
| " Outputs '42' | ||
| Promise.resolve(Promise.reject('ERROR!')) | ||
| \.catch({reason -> execute('echo ' . reason)}) |
tyru
Dec 26, 2017
Member
execute('echo reason') か execute('echo ' . string(reason)) ?
execute('echo reason') か execute('echo ' . string(reason)) ?
| \] | ||
| \) | ||
| \.then({-> exeute('echom "All repositories were successfully cloned!"', '')}) | ||
| \.catch({err -> execute('echom "Failed to clone: " . ' string(err), '')}) |
tyru
Dec 26, 2017
Member
. が足らないです。
execute('echom "Failed to clone: " . ' . string(err), '')
これでもいいかも?
execute('echom "Failed to clone:" ' . string(err), '')
. が足らないです。
execute('echom "Failed to clone: " . ' . string(err), '')
これでもいいかも?
execute('echom "Failed to clone:" ' . string(err), '')
|
僕はフィードバックの内容と今後の変更内容(予定)に特に意見はないので Approve にしておきます。 |
| \ code ? reject(buf.err) : resolve(buf.out) | ||
| \ }, | ||
| \ })}) | ||
| endfunction |
tyru
Dec 26, 2017
Member
自分も詳しく job の仕様を把握してませんが、試した限りだと exit_cb が close_cb よりも後に実行されるとは限らないようです。
https://gist.github.com/tyru/0ffee5ba2b8cab7390f2652c9a97bc24
上記の結果 (messages) だと exit_cb のが先に実行されてしまっているため、 buf が空になってしまっています。
close_cb のみ指定して s:read_to_buf() に resolve してもらうとかですかね? (それでいいという確証はないです…)
自分も詳しく job の仕様を把握してませんが、試した限りだと exit_cb が close_cb よりも後に実行されるとは限らないようです。
https://gist.github.com/tyru/0ffee5ba2b8cab7390f2652c9a97bc24
上記の結果 (messages) だと exit_cb のが先に実行されてしまっているため、 buf が空になってしまっています。
close_cb のみ指定して s:read_to_buf() に resolve してもらうとかですかね? (それでいいという確証はないです…)
rhysd
Dec 26, 2017
•
Author
Contributor
私の環境だとコールバックの発火順は exit_cb のほうが後なので,どうやらプラットフォーム依存ですね…ちなみに close_cb だけだと exit status が分からないのでコマンドが失敗したかどうか分からないです.
ちゃんとやるならどちらの順序で発火しても動くようにすべきですが,それをやると example としてイマイチになってしまう気がします(本質的でないコードが増えすぎる).なにか良い修正案とかないでしょうか… @lambdalisue @thinca @mattn
私の環境だとコールバックの発火順は exit_cb のほうが後なので,どうやらプラットフォーム依存ですね…ちなみに close_cb だけだと exit status が分からないのでコマンドが失敗したかどうか分からないです.
ちゃんとやるならどちらの順序で発火しても動くようにすべきですが,それをやると example としてイマイチになってしまう気がします(本質的でないコードが増えすぎる).なにか良い修正案とかないでしょうか… @lambdalisue @thinca @mattn
lambdalisue
Dec 26, 2017
•
Member
プロセスの終了とチャネルのクローズはそもそも別の物なので例として不適かと思います。
プロセスが終了した後に全ての出力が欲しいのであれば、オプションに 'drop': 'never' を渡してチャネルから ch_read で持ってくるのが良いかと思います
プロセスの終了とチャネルのクローズはそもそも別の物なので例として不適かと思います。
プロセスが終了した後に全ての出力が欲しいのであれば、オプションに 'drop': 'never' を渡してチャネルから ch_read で持ってくるのが良いかと思います
lambdalisue
Dec 26, 2017
•
Member
あと、プロセスの終了はどうでもよくて出力が全部欲しい場合は Vim のヘルプにある通り close_cb でやるべきですかね。
http://vim-jp.org/vimdoc-ja/channel.html#read-in-close-cb
個人的には実用面で言えば「プロセスの出力結果がほしい」が要求であって「プロセスが終了してから出力結果がほしい」ではないと思います。なので例としては、こちらのほうが適切かと思います。
あと、プロセスの終了はどうでもよくて出力が全部欲しい場合は Vim のヘルプにある通り close_cb でやるべきですかね。
http://vim-jp.org/vimdoc-ja/channel.html#read-in-close-cb
個人的には実用面で言えば「プロセスの出力結果がほしい」が要求であって「プロセスが終了してから出力結果がほしい」ではないと思います。なので例としては、こちらのほうが適切かと思います。
mattn
Dec 26, 2017
Member
すでに喋るべき内容は語られていた。
すでに喋るべき内容は語られていた。
rhysd
Dec 27, 2017
•
Author
Contributor
お手数ですが,これで動いているか確認いただけませんでしょうか?
自分の環境では元のコードも動いてしまっていたので,これで問題が解決したのかどうかが確認できず…
let s:Promise = vital#vital#import('Async.Promise')
function! s:read(chan, part) abort
let out = ''
while ch_status(a:chan, {'part' : a:part}) ==# 'buffered'
let out .= ch_read(a:chan, {'part' : a:part})
endwhile
return out
endfunction
function! s:sh(...) abort
let cmd = join(a:000, ' ')
let buf = {}
return s:Promise.new({resolve, reject -> job_start(cmd, {
\ 'drop' : 'never',
\ 'close_cb' : {ch -> 'do nothing' },
\ 'exit_cb' : {ch, code ->
\ code ?
\ reject(s:read(ch, 'err')) :
\ resolve(s:read(ch, 'out'))
\ },
\ })})
endfunction
call s:sh('ls', '-l')
\.then({out -> execute('echom ' . string('Output: ' . out), '')})
\.catch({err -> execute('echom ' . string('Error: ' . err), '')})
ちなみに,close_cb は指定しないと job の開始に失敗してしまいました
お手数ですが,これで動いているか確認いただけませんでしょうか?
自分の環境では元のコードも動いてしまっていたので,これで問題が解決したのかどうかが確認できず…
let s:Promise = vital#vital#import('Async.Promise')
function! s:read(chan, part) abort
let out = ''
while ch_status(a:chan, {'part' : a:part}) ==# 'buffered'
let out .= ch_read(a:chan, {'part' : a:part})
endwhile
return out
endfunction
function! s:sh(...) abort
let cmd = join(a:000, ' ')
let buf = {}
return s:Promise.new({resolve, reject -> job_start(cmd, {
\ 'drop' : 'never',
\ 'close_cb' : {ch -> 'do nothing' },
\ 'exit_cb' : {ch, code ->
\ code ?
\ reject(s:read(ch, 'err')) :
\ resolve(s:read(ch, 'out'))
\ },
\ })})
endfunction
call s:sh('ls', '-l')
\.then({out -> execute('echom ' . string('Output: ' . out), '')})
\.catch({err -> execute('echom ' . string('Error: ' . err), '')})ちなみに,close_cb は指定しないと job の開始に失敗してしまいました
tyru
Dec 27, 2017
•
Member
@rhysd 動きました。ただいくつか修正した方が良さそうな点がありました。
open の場合も考慮する
ch_status(a:chan, {'part' : a:part}) =~# 'open\|buffered'
- 末尾に改行入れる
let out .= ch_read(a:chan, {'part' : a:part}) . "\n"
:echom だと改行を含む文字列の場合つらい感じになるので :echo のがいいかもです (最後の .then() と .catch() の中)
ちなみに,close_cb は指定しないと job の開始に失敗してしまいました
これ自分の環境 (WSL) でも再現しました。詳しく追ってないですが Error: みたいに err が空文字で渡ってきちゃいますね。
@rhysd 動きました。ただいくつか修正した方が良さそうな点がありました。
openの場合も考慮するch_status(a:chan, {'part' : a:part}) =~# 'open\|buffered'
- 末尾に改行入れる
let out .= ch_read(a:chan, {'part' : a:part}) . "\n"
:echomだと改行を含む文字列の場合つらい感じになるので:echoのがいいかもです (最後の.then()と.catch()の中)
ちなみに,
close_cbは指定しないと job の開始に失敗してしまいました
これ自分の環境 (WSL) でも再現しました。詳しく追ってないですが Error: みたいに err が空文字で渡ってきちゃいますね。
tyru
Dec 27, 2017
Member
Twitter でも言いましたが、このコードだとエラーにならなかったです。
もしかして job 以外の所でコケて .catch() に来てるかも?
https://gist.github.com/tyru/4707726873f3e4955f331109f9dc0734
Twitter でも言いましたが、このコードだとエラーにならなかったです。
もしかして job 以外の所でコケて .catch() に来てるかも?
https://gist.github.com/tyru/4707726873f3e4955f331109f9dc0734
rhysd
Dec 27, 2017
Author
Contributor
私の手元で確認した限りでは channel status が fail になっていたので job の開始に失敗しているんじゃないかと思います.他の箇所で例外が起きているなら err が空になるのは(Promise の実装がバグってない限り)おかしいですね
私の手元で確認した限りでは channel status が fail になっていたので job の開始に失敗しているんじゃないかと思います.他の箇所で例外が起きているなら err が空になるのは(Promise の実装がバグってない限り)おかしいですね
lambdalisue
Dec 27, 2017
•
Member
動きました。修正点としては @tyru さんと同意見で、以下がいいかなと思います。
let s:Promise = vital#vital#import('Async.Promise')
function! s:read(ch, part) abort
let out = []
while ch_status(a:ch, {'part' : a:part}) =~# '^\%(open\|buffered\)$'
call add(out, ch_read(a:ch, {'part': a:part}))
endwhile
return join(out, "\n")
endfunction
function! s:sh(...) abort
let cmd = join(a:000, ' ')
return s:Promise.new({resolve, reject -> job_start(cmd, {
\ 'drop' : 'never',
\ 'close_cb' : {ch -> 'do nothing' },
\ 'exit_cb' : {ch, code ->
\ code ?
\ reject(s:read(ch, 'err')) :
\ resolve(s:read(ch, 'out'))
\ },
\ })})
endfunction
call s:sh('ls', '-l')
\.then({out -> execute('echo ' . string('Output: ' . out), '')})
\.catch({err -> execute('echo ' . string('Error: ' . err), '')})
あと s:sh('cat', '/foo/bar') でちゃんと Error: cat: /foo/bar: No such file or directory が出ました。
環境: macOS High Sierra
動きました。修正点としては @tyru さんと同意見で、以下がいいかなと思います。
let s:Promise = vital#vital#import('Async.Promise')
function! s:read(ch, part) abort
let out = []
while ch_status(a:ch, {'part' : a:part}) =~# '^\%(open\|buffered\)$'
call add(out, ch_read(a:ch, {'part': a:part}))
endwhile
return join(out, "\n")
endfunction
function! s:sh(...) abort
let cmd = join(a:000, ' ')
return s:Promise.new({resolve, reject -> job_start(cmd, {
\ 'drop' : 'never',
\ 'close_cb' : {ch -> 'do nothing' },
\ 'exit_cb' : {ch, code ->
\ code ?
\ reject(s:read(ch, 'err')) :
\ resolve(s:read(ch, 'out'))
\ },
\ })})
endfunction
call s:sh('ls', '-l')
\.then({out -> execute('echo ' . string('Output: ' . out), '')})
\.catch({err -> execute('echo ' . string('Error: ' . err), '')})あと s:sh('cat', '/foo/bar') でちゃんと Error: cat: /foo/bar: No such file or directory が出ました。
環境: macOS High Sierra
|
let P = vital#vital#import('Async.Promise')
let ps = map(range(1000), {i -> g:P.new({res -> timer_start(i, res)})})
" race()
let amount = 0.0
let total = 10
let i = 0
while i < 10
let stamp = reltime()
call P.race(ps)
let amount += str2float(reltimestr(reltime(stamp)))
let i += 1
endwhile
echom 'amount: ' . string(amount)
echom 'avg: ' . string(amount / 10.0)
" all()
let amount = 0.0
let total = 10
let i = 0
while i < 10
let stamp = reltime()
call P.all(ps)
let amount += str2float(reltimestr(reltime(stamp)))
let i += 1
endwhile
echom 'amount: ' . string(amount)
echom 'avg: ' . string(amount / 10.0)
|
僕のベンチでも 100000 回とかでやっと差が出てる感じなので把握した上で |
|
@lambdalisue 了解です.もし今後実際に使ってみてそこで問題があることが分かったらその時に対処しましょう |
|
|
| endif | ||
| return child | ||
| endfunction | ||
| let s:PROMISE.then = function('s:_promise_then') |
haya14busa
Dec 27, 2017
Member
実は vital core でもやってます
vital.vim/autoload/vital/vital.vim
Line 76
in
e553b10
書くの面倒だという以外はいいことしかないと思ってる
実は vital core でもやってます
vital.vim/autoload/vital/vital.vim
Line 76 in e553b10
書くの面倒だという以外はいいことしかないと思ってる
| try | ||
| let Result = a:callback(a:result) | ||
| catch | ||
| let Err = v:exception |
haya14busa
Dec 27, 2017
Member
v:exeption は関数ではないので関係ないのでは? Result は関数になりうりそうなので l: つけておいてもよさそう。
あと関連して Err が funcref にならないなら大文字にする必要はないとおもいます. 逆に funcref なら大文字かつ l: はつけるべき。
v:exeption は関数ではないので関係ないのでは? Result は関数になりうりそうなので l: つけておいてもよさそう。
あと関連して Err が funcref にならないなら大文字にする必要はないとおもいます. 逆に funcref なら大文字かつ l: はつけるべき。
| try | ||
| let Result = a:callback(a:result) | ||
| catch | ||
| let Err = v:exception |
haya14busa
Dec 27, 2017
Member
これだとスタックトレースの情報 (v:throwpoint) が消えてしまうのでエラーオブジェクト作って v:exeption と v:throwpoint 両方持たせておいたほうがよさそう?
これだとスタックトレースの情報 (v:throwpoint) が消えてしまうのでエラーオブジェクト作って v:exeption と v:throwpoint 両方持たせておいたほうがよさそう?
haya14busa
Dec 27, 2017
Member
[追記補足] これ"よさそう?"って微妙なカンジで最初は書いてましたが、help の github_issues の例とか実は僕も動かなくてデバックしてて、スタックトレースだしてもムズい(lambdaをおえない...)のにエラーだけだとほんとによくわからないエラーしかでないことがあるので、真剣に検討してもいいかなぁと思います。
[追記補足] これ"よさそう?"って微妙なカンジで最初は書いてましたが、help の github_issues の例とか実は僕も動かなくてデバックしてて、スタックトレースだしてもムズい(lambdaをおえない...)のにエラーだけだとほんとによくわからないエラーしかでないことがあるので、真剣に検討してもいいかなぁと思います。
rhysd
Dec 27, 2017
Author
Contributor
うーん,それはエラーオブジェクトを定義する必要が出てしまいますね…例外が起きたら v:exception だけでなくて,v:throwpoint も結合させた文字列で reject するとかどうでしょう?
うーん,それはエラーオブジェクトを定義する必要が出てしまいますね…例外が起きたら v:exception だけでなくて,v:throwpoint も結合させた文字列で reject するとかどうでしょう?
lambdalisue
Dec 27, 2017
Member
僕は @haya14busa さんのいうエラーオブジェクトのほうが良いかと思います。
分離されたデータをライブラリに勝手に結合されてイラッとした経験結構多いので……(例: stdout と stderr を勝手に結合するやつとか)
僕は @haya14busa さんのいうエラーオブジェクトのほうが良いかと思います。
分離されたデータをライブラリに勝手に結合されてイラッとした経験結構多いので……(例: stdout と stderr を勝手に結合するやつとか)
haya14busa
Dec 27, 2017
Member
{ 'message': v:exception [, 'throwpoint': v:throwpoint]} くらいでどうでしょうか. 文字列結合はできるなら避けたい
{ 'message': v:exception [, 'throwpoint': v:throwpoint]} くらいでどうでしょうか. 文字列結合はできるなら避けたい
| endfunction | ||
|
|
||
| function! s:is_available() abort | ||
| return has('nvim') || v:version >= 800 |
haya14busa
Dec 27, 2017
Member
nvim よく追えてないですが lambda は割りと最近入ってるはずなのでチェックとしてどれくらい機能してるか微妙かも?
has('lambda') とかのほうがいいかなと思います. あとは v:t_ や timer_startも使ってはいるので 8 以上っていうのは残してもよいかな?
nvim よく追えてないですが lambda は割りと最近入ってるはずなのでチェックとしてどれくらい機能してるか微妙かも?
has('lambda') とかのほうがいいかなと思います. あとは v:t_ や timer_startも使ってはいるので 8 以上っていうのは残してもよいかな?
rhysd
Dec 27, 2017
•
Author
Contributor
うーん,なるほど.確かにそのとおりですね.僕も具体的にどのバージョンかまでは把握していないです… 機能ベースで言うと,has('lambda') && has('timers') のほうが良さそうですね
うーん,なるほど.確かにそのとおりですね.僕も具体的にどのバージョンかまでは把握していないです… 機能ベースで言うと,has('lambda') && has('timers') のほうが良さそうですね
haya14busa
Dec 27, 2017
Member
+1 to has('lambda') && has('timers') .
一般的にも機能ベースでチェックして、それでもできないならバージョンでチェックするというのがいいと思います
+1 to has('lambda') && has('timers') .
一般的にも機能ベースでチェックして、それでもできないならバージョンでチェックするというのがいいと思います
| be |Funcref| and they are guaranteed to be called __asynchronously__. | ||
| > | ||
| echo 'hi' | ||
| Promise.new({resolve -> execute('echo "halo"') || resolve(42)}) |
haya14busa
Dec 27, 2017
Member
上下に echoあって実行できそうにみえるし call Promise or call s:Promise にしておいてもよさそう。
この help 全体的に call なしで Promise.new.... としているけどわざとですかね?
せっかくなら call つけてほぼそのまま実行できるくらいにしておいてもよいかなと思いますがどっちでもよさそう
あと execute('...', '') にしないとこの結果を確認できないので直しておいてあげると読者に優しそうですかね。
上下に echoあって実行できそうにみえるし call Promise or call s:Promise にしておいてもよさそう。
この help 全体的に call なしで Promise.new.... としているけどわざとですかね?
せっかくなら call つけてほぼそのまま実行できるくらいにしておいてもよいかなと思いますがどっちでもよさそう
あと execute('...', '') にしないとこの結果を確認できないので直しておいてあげると読者に優しそうですかね。
rhysd
Dec 27, 2017
Author
Contributor
はい,第2引数は各所で忘れていたので修正します…ありがとうございます. :call も追加します
はい,第2引数は各所で忘れていたので修正します…ありがとうございます. :call も追加します
haya14busa
Dec 27, 2017
•
Member
[補足] 面倒そうなので一度 s:echom() とか定義してもいいかなともちょっと感じましたがおまかせします
[補足] 面倒そうなので一度 s:echom() とか定義してもいいかなともちょっと感じましたがおまかせします
| \ s:sh('git', 'clone', 'https://github.com/rhysd/clever-f.vim.git'), | ||
| \] | ||
| \) | ||
| \.then({-> exeute('echom "All repositories were successfully cloned!"', '')}) |
haya14busa
Dec 27, 2017
Member
[nit] s/exeute/execute/
[nit] s/exeute/execute/
rhysd
Dec 27, 2017
•
Author
Contributor
nice catch です.misspell さん頼む…
nice catch です.misspell さん頼む…
| let s:Promise = vital#vital#import('Async.Promise') | ||
| function! s:wait(ms) | ||
| return s:Promise.new({resolve -> timer_start(a:ms)}) |
haya14busa
Dec 27, 2017
Member
return s:Promise.new({resolve -> timer_start(a:ms, resolve)}) ですかね。 resolve できてない
return s:Promise.new({resolve -> timer_start(a:ms, resolve)}) ですかね。 resolve できてない
rhysd
Dec 27, 2017
Author
Contributor
その通りです.ありがとうございます.
その通りです.ありがとうございます.
| function! s:_notify_done(wg, index, value) abort | ||
| let a:wg.done[a:index] = a:value | ||
| let a:wg.resolved += 1 | ||
| if a:wg.resolved == a:wg.total |
haya14busa
Dec 27, 2017
Member
[nit] total じゃなくて remaining にしてデクリメントしていって0ならresolveってすると一つ変数を減らせる説 (total, resolved -> remaining)
[nit] total じゃなくて remaining にしてデクリメントしていって0ならresolveってすると一つ変数を減らせる説 (total, resolved -> remaining)
rhysd
Dec 27, 2017
Author
Contributor
あー,確かに.キーの数は減らしたいのでそうしてみます.
あー,確かに.キーの数は減らしたいのでそうしてみます.
| endif | ||
|
|
||
| let wait_group = { | ||
| \ 'done': repeat([v:null], total), |
haya14busa
Dec 27, 2017
Member
[nit] results とかのほうがわかりやすい気がしますがどうでしょう? done って done フラグ感がある(Go脳かもしれない) これもes6-promise では results だった
[nit] results とかのほうがわかりやすい気がしますがどうでしょう? done って done フラグ感がある(Go脳かもしれない) これもes6-promise では results だった
|
|
||
| function! s:resolve(...) abort | ||
| let promise = s:new(s:NOOP) | ||
| call s:_resolve(promise, a:0 > 0 ? a:1 : v:null) |
haya14busa
Dec 27, 2017
Member
[nit] get(a:000, 0, v:null) とか他のところで使ってるし書き方統一してもよさそう。
(個人的には get(a:, 1, v:null) が好みだけど統一されてたらなんでもok)
[nit] get(a:000, 0, v:null) とか他のところで使ってるし書き方統一してもよさそう。
(個人的には get(a:, 1, v:null) が好みだけど統一されてたらなんでもok)
rhysd
Dec 28, 2017
Author
Contributor
個人的な好みにより a:0 になりました
個人的な好みにより a:0 になりました
haya14busa
Dec 31, 2017
Member
[感想] 自分がいちばん好みじゃないの来た...! (勿論直してほしいとかではなくて単なる感想で、言い分としてはa:0は考えることが増えるのと(a:0が数でぇ...+1を見に行く...)、ほかの get or default イディオムと合わせたいので自分は get(a:, n, default) を使ってる (g: とかと一緒のイディオムにする)
[感想] 自分がいちばん好みじゃないの来た...! (勿論直してほしいとかではなくて単なる感想で、言い分としてはa:0は考えることが増えるのと(a:0が数でぇ...+1を見に行く...)、ほかの get or default イディオムと合わせたいので自分は get(a:, n, default) を使ってる (g: とかと一緒のイディオムにする)
| let a:parent._fulfillments += [ a:on_fulfilled ] | ||
| let a:parent._rejections += [ a:on_rejected ] | ||
| if is_empty && a:parent._state > s:PENDING | ||
| call timer_start(0, function('s:_publish', [a:parent])) |
haya14busa
Dec 27, 2017
Member
[optional] call timer_start(0, ...) っていうイディオム関数化しておいてもよさそう? asap とか next_tick とか?
読者が暗黙に timer_start(0,...) がどういう意味かわかってるという仮定をおいてるけど関数化されててコメントもあったりすると可読性よさそう
[optional] call timer_start(0, ...) っていうイディオム関数化しておいてもよさそう? asap とか next_tick とか?
読者が暗黙に timer_start(0,...) がどういう意味かわかってるという仮定をおいてるけど関数化されててコメントもあったりすると可読性よさそう
lambdalisue
Dec 27, 2017
Member
とても気持ちはわかるのですが
- ユーザー定義関数のオーバーヘッドが大きい
- 無駄にコールスタックを積んでしまう
の二点から今のままが良いと思います。
と同じで Vim script の事情から本来不要なハックや最適化が必要になるの少しモニョりますが、仕方ない……
とても気持ちはわかるのですが
- ユーザー定義関数のオーバーヘッドが大きい
- 無駄にコールスタックを積んでしまう
の二点から今のままが良いと思います。
と同じで Vim script の事情から本来不要なハックや最適化が必要になるの少しモニョりますが、仕方ない……
haya14busa
Dec 27, 2017
Member
その最適化が本当に必要な箇所かどうかが結構あやしいとは思いますが(2はなんかchainしすぎるとダメとか見た気がするのでわからんでもない), まぁoptionalですね。
その最適化が本当に必要な箇所かどうかが結構あやしいとは思いますが(2はなんかchainしすぎるとダメとか見た気がするのでわからんでもない), まぁoptionalですね。
lambdalisue
Dec 27, 2017
Member
はい。ここがユーザーが触る場所ならば僕も関数でラップする事には同意なのですが、ライブラリ内部のコードで(個人の主観としては)ほぼイディオム的な使い方なのでラップする必要が薄いと思います。
はい。ここがユーザーが触る場所ならば僕も関数でラップする事には同意なのですが、ライブラリ内部のコードで(個人の主観としては)ほぼイディオム的な使い方なのでラップする必要が薄いと思います。
rhysd
Dec 27, 2017
Author
Contributor
提案ありがとうございます.個人的な感覚では timer_start(0, ...) のままで良いかなと思います.0秒のタイマーでも意味は通じると思いますし,ラップして関数1つ増やす保守コストに見合うほどではないなと思いました.
JavaScript では確かに asap とか next_tick みたいにラップすることが多いのですが,それは環境によって下が setImmediate() だったり setTimeout() だったりするからなのですよね(使えるなら setImmediate のほうが良いため)
提案ありがとうございます.個人的な感覚では timer_start(0, ...) のままで良いかなと思います.0秒のタイマーでも意味は通じると思いますし,ラップして関数1つ増やす保守コストに見合うほどではないなと思いました.
JavaScript では確かに asap とか next_tick みたいにラップすることが多いのですが,それは環境によって下が setImmediate() だったり setTimeout() だったりするからなのですよね(使えるなら setImmediate のほうが良いため)
| elseif settled == s:REJECTED | ||
| let l:CB = a:promise._rejections[i] | ||
| else | ||
| throw 'vital: Async.Promise: Cannot publish a pending promise' |
haya14busa
Dec 27, 2017
Member
このチェックはfor loop の外で settled 変数に入れたあと or 前くらいがよさそう
このチェックはfor loop の外で settled 変数に入れたあと or 前くらいがよさそう
rhysd
Dec 27, 2017
Author
Contributor
publish の呼ばれ方的にこのパスが呼ばれることがない(防衛的に入れてた)ので気づきませんでしたが,確かにそうですね.ループ前にチェックするようにします.
publish の呼ばれ方的にこのパスが呼ばれることがない(防衛的に入れてた)ので気づきませんでしたが,確かにそうですね.ループ前にチェックするようにします.
|
|
||
| It should create with default value(=v:null) when no argument is given to resolve()/reject() | ||
| let l = l: | ||
| call P.new({resolve -> resolve()}).then({x -> extend(l, {'done' : x})}) |
haya14busa
Dec 27, 2017
Member
[nit][optional] 実装のほうにも似たコメント書きましたが done より result 感がある?
[nit][optional] 実装のほうにも似たコメント書きましたが done より result 感がある?
rhysd
Dec 28, 2017
Author
Contributor
確かに.フラグをセットするんじゃなくて値をセットしてるとこは result にしといたほうが値の意図が明確で良さそうです
確かに.フラグをセットするんじゃなくて値をセットしてるとこは result にしといたほうが値の意図が明確で良さそうです
| Assert Equals(p._state, PENDING) | ||
| let p = p.then({-> extend(l, {'done' : 1})}) | ||
| Assert Equals(p._state, PENDING) | ||
| Assert Equals(done, 0) |
haya14busa
Dec 27, 2017
Member
あってるので別によさそうだけど, call s:wait_has_key(l, 'done') してあげないとフェアじゃないかも?
...と思ったけど無駄にunit test が長くなるのもアレだしムズカシイ
あってるので別によさそうだけど, call s:wait_has_key(l, 'done') してあげないとフェアじゃないかも?
...と思ったけど無駄にunit test が長くなるのもアレだしムズカシイ
haya14busa
Dec 27, 2017
Member
(見直すとそもそも wait_has_key は done すでに定義してるので使えなかった.... ので sleep 1ms とかするとフェア感?(本当か?)
(見直すとそもそも wait_has_key は done すでに定義してるので使えなかった.... ので sleep 1ms とかするとフェア感?(本当か?)
rhysd
Dec 28, 2017
Author
Contributor
ここは待たなくても timer_start でキューイングされた処理は後のどこかで無害な感じで実行されて終わるだけなので,待たなくて良いかなと
ここは待たなくても timer_start でキューイングされた処理は後のどこかで無害な感じで実行されて終わるだけなので,待たなくて良いかなと
rhysd
Dec 30, 2017
Author
Contributor
あーすみません,このコメントの意図を読み違えてました..then がタイマーを挟むようになった今,確かにある程度待たないと .then が発火していないことを確認するのにはこれでは不十分でした.ちなみに .then は永遠に発火しないので,done が定義済みかによらず wait_has_key は使えないです.
あーすみません,このコメントの意図を読み違えてました..then がタイマーを挟むようになった今,確かにある程度待たないと .then が発火していないことを確認するのにはこれでは不十分でした.ちなみに .then は永遠に発火しないので,done が定義済みかによらず wait_has_key は使えないです.
| let p = P.new({resolve -> resolve(Val)}) | ||
| Assert Equals(p._state, FULFILLED) | ||
| let p2 = p.then({x -> x}).then({r -> extend(l, {'Done' : r})}) | ||
| call s:wait_has_key(l, 'Done') |
haya14busa
Dec 27, 2017
Member
wait_has_key の前でまだ Done には値入ってないことを確認すると "asynchronously" なことがわかりやすくてよさそう
wait_has_key の前でまだ Done には値入ってないことを確認すると "asynchronously" なことがわかりやすくてよさそう
| for l:Val in [42, 'foo', {'foo': 42}, {}, [1, 2, 3], [], function('empty')] | ||
| let p = P.new({_, reject -> reject(Val)}) | ||
| let p2 = p.then({-> extend(l, {'Done' : 'Error: resolved to .then'})}).catch({v -> extend(l, {'Done' : v})}) | ||
| call s:wait_has_key(l, 'Done') |
haya14busa
Dec 27, 2017
Member
ここも wait_has_key のまえにチェック入れたほうが asynchronouslyのテストであることがわかりやすくてよさそう?
ここも wait_has_key のまえにチェック入れたほうが asynchronouslyのテストであることがわかりやすくてよさそう?
| Assert Equals(done, 42) | ||
| End | ||
|
|
||
| It can take rejection handler at 2nd parameter |
haya14busa
Dec 27, 2017
Member
このテスト catch 使ってないし Describe .then のとこにあったほうがよさそう
このテスト catch 使ってないし Describe .then のとこにあったほうがよさそう
rhysd
Dec 28, 2017
Author
Contributor
これは .then のとこに置くべきなのを間違えてますね…ありがとうございます!
これは .then のとこに置くべきなのを間違えてますね…ありがとうございます!
|
今気が付きましたが 8f6b866 は Neovim を捨てることになります。 Neovim v0.2.3-240-g6ff13d (ほぼ現在の HEAD) で
この辺のサポートは結構あとだった気がするので |
|
@lambdalisue うお,マジですか.8f6b866 に強い動機があるわけではないため,削除しようと思います.ご指摘ありがとうございます. |
|
8f6b866 を削除するために たくさんのレビューありがとうございます.2時を回ったので,今日はこのあたり( #567 (comment) )にしてまた明日の夜に修正予定です. 目下のところ,議論すべきは |
| endfunction | ||
|
|
||
| function! s:_subscribe(parent, child, on_fulfilled, on_rejected) abort | ||
| let is_empty = empty(a:parent._children) |
vim-jp-bot
Dec 28, 2017
[vimlint] reported by reviewdog 🐶
EVL102: unused variable l:is_empty
[vimlint] reported by reviewdog
EVL102: unused variable l:is_empty
Discussion point: Error HandlingProblem: call s:P.new({-> execute('throw "oops"')})
\.catch({err -> execute('echo err', '')})We can refer 1. Concat
|
|
テストまで見ました。 |
| be |Funcref| and they are guaranteed to be called __asynchronously__. | ||
| > | ||
| echo 'hi' | ||
| call Promise.new({resolve -> execute('echo "halo"', '') || resolve(42)}) |
thinca
Dec 31, 2017
Member
この書き方たと、例えば echo "halo" の部分が echon "1halo" だと || の左側が TRUE になるので resolve(42) が実行されません。つまりたまたまうまく動くだけのように見えます…。
そうと知らずにこの例をコピーして使う人が出てくることを危惧しています。
この書き方たと、例えば echo "halo" の部分が echon "1halo" だと || の左側が TRUE になるので resolve(42) が実行されません。つまりたまたまうまく動くだけのように見えます…。
そうと知らずにこの例をコピーして使う人が出てくることを危惧しています。
| - Operation has completed successfully | ||
| - Operation has failed with an error | ||
|
|
||
| {promise}.then([{onResolved} [, {onRejected}]]) |
thinca
Dec 31, 2017
Member
なるほど。この help 内でも Fulfilled Promise のようなワードがあったので、resolve の方が表記が揺れなくてわかりやすそうという意図なら、これらも Resolved Promise にするのがいいかも?
とは言え私もそこまで強い意志はないです。意図してないうっかりだったらアレなので念のため確認くらいの気持ちでした。
なるほど。この help 内でも Fulfilled Promise のようなワードがあったので、resolve の方が表記が揺れなくてわかりやすそうという意図なら、これらも Resolved Promise にするのがいいかも?
とは言え私もそこまで強い意志はないです。意図してないうっかりだったらアレなので念のため確認くらいの気持ちでした。
| function! s:wait_has_key(obj, name) abort | ||
| " if has_key(a:obj, a:name) | ||
| " throw printf('s:wait_has_key(): Key "%s" is already in object %s', a:name, a:obj) | ||
| " endif |
thinca
Dec 31, 2017
Member
このコメントは必要なやつでしょうか?
このコメントは必要なやつでしょうか?
rhysd
Dec 31, 2017
Author
Contributor
必要ないやつでした…
必要ないやつでした…
| let l = l: | ||
| call P.new({resolve -> resolve()}).then({x -> extend(l, {'result' : x})}) | ||
| call s:wait_has_key(l, 'result') | ||
| " :Assert Equal() does not support v:null by themis.vim |
thinca
Dec 31, 2017
Member
一応 master に入ってる最新版 themis.vim ならいけるはずです(が、別に無理にしなくて OK)。
一応 master に入ってる最新版 themis.vim ならいけるはずです(が、別に無理にしなくて OK)。
rhysd
Dec 31, 2017
Author
Contributor
おっと,そうですね.rebase して最新の themis を使うようにします.
おっと,そうですね.rebase して最新の themis を使うようにします.
| End | ||
|
|
||
| It should make settled Promise with v:null when no argument is given to resolve()/reject() | ||
| let l = l: |
thinca
Dec 31, 2017
Member
私が言い出しておいてアレですが、この使い方なら無理に l: 使わなくても新規の辞書 {} でも良さそう…。(その場合は Assert の部分が l.result になる)
私が言い出しておいてアレですが、この使い方なら無理に l: 使わなくても新規の辞書 {} でも良さそう…。(その場合は Assert の部分が l.result になる)
rhysd
Dec 31, 2017
Author
Contributor
分かる… うーん,ただ l. 付けなくて良い利点はあるんですよね.特に理由が無ければこのままでも良いかなぁと
分かる… うーん,ただ l. 付けなくて良い利点はあるんですよね.特に理由が無ければこのままでも良いかなぁと
thinca
Jan 1, 2018
Member
ハック感が抜ける(…までは行かなくても薄れる)メリットはありそうですが、全部直すの面倒なのも同意なのでこのままでも OK です 🙆
ハック感が抜ける(…までは行かなくても薄れる)メリットはありそうですが、全部直すの面倒なのも同意なのでこのままでも OK です
| End | ||
| End | ||
|
|
||
| Describe .then() |
thinca
Dec 31, 2017
Member
.then() と .catch() は Promise オブジェクトのメソッド(モジュール直下の関数ではない)ので、別途 Describe Promise object 的なブロックに入れるのが良さそう。
.then() と .catch() は Promise オブジェクトのメソッド(モジュール直下の関数ではない)ので、別途 Describe Promise object 的なブロックに入れるのが良さそう。
| End | ||
| End | ||
|
|
||
| Describe .reject() |
thinca
Dec 31, 2017
Member
.reject() に resolved Promise を渡した場合のテストがほしいなー。
.reject() に resolved Promise を渡した場合のテストがほしいなー。
|
間に合わなかった! |
| let s:REJECTED = 2 | ||
|
|
||
| let s:DICT_T = type({}) | ||
| let s:NULL_T = type(v:null) |
thinca
Dec 31, 2017
Member
これわざわざ type() 使ってますが、仕様上 v:null を使うことになっているので foo is v:null なチェックでも良さそうな気がします。そうすれば左辺値に type() を適用する必要がなくなるのでほんの少し速くなるかも。
これわざわざ type() 使ってますが、仕様上 v:null を使うことになっているので foo is v:null なチェックでも良さそうな気がします。そうすれば左辺値に type() を適用する必要がなくなるのでほんの少し速くなるかも。
rhysd
Dec 31, 2017
Author
Contributor
確かにわざわざ型の ID を見ているのは冗長ですね.ありがとうございます,直します.
確かにわざわざ型の ID を見ているのは冗長ですね.ありがとうございます,直します.
| call s:_fulfill(a:promise, Result) | ||
| elseif a:settled == s:REJECTED | ||
| call s:_reject(a:promise, Result) | ||
| endif |
thinca
Dec 31, 2017
Member
💭 [IMO]
ちょっと読んでて混乱しました。
最初の pending の判定を除くと、上2つが has_callback が TRUE の場合、下 2 つが has_callback が FALSE の場合なので、L43 の if の中にそれぞれ書いてしまっても良いかも。
その場合 pending の判定が重複しちゃうのでどちらが良いかむずいですが… 中に入れてしまえば L54 の不要な代入も自然に減らせそうな気がします。
ちょっと読んでて混乱しました。
最初の pending の判定を除くと、上2つが has_callback が TRUE の場合、下 2 つが has_callback が FALSE の場合なので、L43 の if の中にそれぞれ書いてしまっても良いかも。
その場合 pending の判定が重複しちゃうのでどちらが良いかむずいですが… 中に入れてしまえば L54 の不要な代入も自然に減らせそうな気がします。
rhysd
Dec 31, 2017
Author
Contributor
確かにここはちょっと冗長かつ微妙な実装になっているのですが,意図としてはコールバックの発火処理と次の(子供の)Promise へチェーンする処理を分離する実装になっています.
a:promise._state != s:PENDING をダブらせて上の if の中で判定するというのも考えたのですが,コールバック実行部分とチェーンの続きを発火する部分が混ざると,それはそれで結構読みづらい印象でした.
確かにここはちょっと冗長かつ微妙な実装になっているのですが,意図としてはコールバックの発火処理と次の(子供の)Promise へチェーンする処理を分離する実装になっています.
a:promise._state != s:PENDING をダブらせて上の if の中で判定するというのも考えたのですが,コールバック実行部分とチェーンの続きを発火する部分が混ざると,それはそれで結構読みづらい印象でした.
|
こちらからは激LGTM |
Discussion for ES6 Promise: promises-aplus/promises-spec#108 Standard ES6 Promise does not check self fulfillment. And in Vim script, there is no way to refer itself in thenable object because Promise object is copy of s:PROMISE.
because it is usually shorter and faster. (As long as I measured, partial is 10% faster than lambda when some variable is captured.
Although previous code has no problem, but linter complains about it.
Thank you @lambdalisue and @tyru!
nit festival! Thank you @haya14busa
|
themis.vim のアップデートの取り込みと fixup になっている箇所の適用のために push -f しました. @thinca 明らかに直したほうが良さそうなところを修正しました.再度修正箇所とレビューコメントの確認をお願いできますでしょうか |
| End | ||
|
|
||
| It should make settled Promise with v:null when no argument is given to resolve()/reject() | ||
| let l = l: |
thinca
Jan 1, 2018
Member
ハック感が抜ける(…までは行かなくても薄れる)メリットはありそうですが、全部直すの面倒なのも同意なのでこのままでも OK です 🙆
ハック感が抜ける(…までは行かなくても薄れる)メリットはありそうですが、全部直すの面倒なのも同意なのでこのままでも OK です
|
では,これでマージしようと思います.細かいところまでチェックいただいた皆さんありがとうございました |

Formed in 2009, the Archive Team (not to be confused with the archive.org Archive-It Team) is a rogue archivist collective dedicated to saving copies of rapidly dying or deleted websites for the sake of history and digital heritage. The group is 100% composed of volunteers and interested parties, and has expanded into a large amount of related projects for saving online and digital history.


This PR takes over #526 because its timeline is too long.
I rebased the branch into 7 commits. Please see #526 for the detail of design of this library.
As described in #526, this PR only implements APIs of ES6 promises. So discussion for adding other (maybe useful) APIs is out of scope. Let's start from small library. After the smallest set of Promise is added to master branch, we can try it in real Vim plugin. And then, let's discuss further rich APIs (like bluebird).
In this PR, I want to
Thank you for your review in advance.
As of #526, either English or Japanese is ok.