◆ メソッドチェーンを直接 Promise でチェーンするとコールバック関数内でメソッドを実行する場合に最後のチェーンよりあとで実行される
  ◆ close とかあると順番が違うとちゃんと動かない
◆ デッドロックになることもある

xxx().open().add().close()

こういう感じでチェーンで使える物を作りたいです
それぞれのメソッドの処理は非同期になります

メソッドが Promise を返すとチェーンができません
Promise を継承したクラスを作って add とか close とかのメソッドを追加という事もできるとは思いますが Promise は普通の Promise にしておきたいのでコントローラになるオブジェクトを返すことにします
クラスっぽくするならこういう感じです

class {
constructor(){
this._promise = Promise.resolve()
}
open(){
this._promise = this._promise.then(value => new Promise((resolve, reject) => {
//
}))
return this
}
move(){
this._promise = this._promise.then(value => new Promise((resolve, reject) => {
//
}))
return this
}
add(){
this._promise = this._promise.then(value => new Promise((resolve, reject) => {
//
}))
return this
}
callback(fn){
this._promise = this._promise.then(fn)
return this
}
close(){
this._promise = thiis._promise.then(value => new Promise((resolve, reject) => {
//
}))
return this
}
}

プロパティに Promise を保存して then でチェーンをつないで this を返します
こうすることでチェーンができるようになるのですが callback 機能を入れると困りました

基本はなにかの処理をするという命令を順番にセットしていくだけなのですが 中には現在の状態を取得するものもあります
そういう場合はそのメソッドにコールバック関数を渡して取得後にその結果を引数として関数を実行するとか callback メソッドを作ってそこにコールバック関数を渡して前の命令の結果を受け取って実行するとかが考えられます

コールバック関数が console.log とか外部関数の呼び出しをするだけなら別に問題もなかったのですが 別のメソッドを呼び出したいことがあります

await contoller.read("path").callback(value => {
console.log(await contoller.xxx().yyy())
}).close()

例えばこういう処理があります
read してその結果を処理するのを callback メソッドに渡した関数で実行してその後に close します

内部的には非同期処理は Promise でチェーンしてるだけで関数の処理自体は即終わってます
read の処理や callback メソッドに渡した関数の処理が実行されるより前に Promise のチェーンに close まで設定されます
なので controller.xxx().yyy() を実行しても xxx の処理は close より後に実行されます

チェーン的には

.read().callback().close().xxx().yyy()

という順番です
close した後なのでものによっては実行できません

Promise をチェーンにせずに別のキューに入れておくというのも考えたのですが キューの処理を開始するという命令が必要になります
最初は Promise.resolve でメソッドチェーンの処理が済んでから開始とできますが 各メソッドが終わり際にキューに処理が残ってれば Promise に then を設定する必要がでてきますし ユーザの操作によって後からメソッドを実行してキューに処理が入ってもそれは自動で開始できません

class {
constructor(){
this._queue = []
this._promise = Promise.resolve(
() => this._promise.then(this._queue.shift())
)
}
open(){
this._queue.push(value => {
//
this._queue.length && this._promise.then(this._queue.shift())
})
return this
}
move(){
this._queue.push(value => {
//
this._queue.length && this._promise.then(this._queue.shift())
})
return this
}
add(){
this._queue.push(value => {
//
this._queue.length && this._promise.then(this._queue.shift())
})
return this
}
callback(fn){
this._queue.push(value => {
fn()
this._queue.length && this._promise.then(this._queue.shift())
})
return this
}
close(){
this._queue.push(value => {
//
this._queue.length && this._promise.then(this._queue.shift())
})
return this
}
}

実行中フラグをもたせて キューに追加すると同時にフラグが立っていないなら即時に実行させるとかいろいろ複雑化してきます
シンプルな考え方でうまくいく方法はないものなのですかね

デッドロック

close のあとに来るというだけでなくデッドロックが発生する問題もあります

class A {
constructor() {
this._promise = Promise.resolve()
}
x() {
this._promise = this._promise.then(
value =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(), 500)
})
)
return this
}
y() {
this._promise = this._promise.then(
value =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(), 500)
})
)
return this
}
callback(fn) {
this._promise = this._promise.then(fn)
return this
}
wait(){
return this._promise
}
}

const a = new A()
a.x()
.callback(async () => {
await a.x().wait()
console.log("実行されない")
})
.y()
await a.wait()

console.log("実行されない")

この 2 つの console.log は実行されません
y の処理がチェーンに設定されるのはすぐですが実際の処理が始まるのは callback メソッドの Promise が resolved になってからです
しかし callback メソッドで設定するコールバック関数内部では a.x().wait() があります
ここの x の処理はチェーン的に y の処理の後に実行されるものです
callback メソッドが終わらないと y は実行されないのに callback メソッドが終わるためには y が終わる必要がある という状態です

コールバック関数の呼び出し待ちという状態なのでその他の処理は動くので 画面でボタンを押したときの処理ならその処理が終わらないだけで別のことはできます
プログラムの強制終了しないといけないみたいな深刻なものってほどでもないですが 動かないという問題はあります

追記

やっぱり気になったのでどうにかできるもの作ってみました
Promise は使いたかったのですが 基本はキューにして callback メソッドみたいなものはサブキューを作ってそこが終わってから本来のキューの消化に進むようにしました

class AsyncController {
constructor() {
this._ctx_stack = []
this._newContext()
}
async _newContext() {
this._ctx_stack.push({
running: false,
queue: [],
})
}
async _newContextWith(fn) {
this._newContext()
await fn()
this._ctx_stack.pop()
}
get _ctx() {
return this._ctx_stack[this._ctx_stack.length - 1]
}
async _next() {
if (!this._ctx.running && this._ctx.queue.length) {
this._ctx.running = true
await this._ctx.queue.shift()()
this._ctx.running = false
this._next()
}
}
add(fn) {
this._ctx.queue.push(fn)
}
x() {
this.add(async () => {
return new Promise(resolve => setTimeout(resolve, 500))
})
this._next()
return this
}
y() {
this.add(async () => {
return new Promise(resolve => setTimeout(resolve, 500))
})
this._next()
return this
}
callback(fn) {
this.add(async () => {
await this._newContextWith(async () => {
await fn()
})
})
this._next()
return this
}
wait() {
return new Promise(resolve => {
this.add(async () => {
resolve()
})
this._next()
})
}
}

const a = new AsyncController()
a.x()
.callback(async () => {
await a.x().wait()
console.log("実行される")
})
.y()
await a.wait()
console.log("実行される")