モーダル被ってませんか?
モーダルを実装するにあたり「表示条件によって被って表示されてしまう」「例外発生時の仕組みがない」という問題に直面したことはありませんか?また、ユーザーステータスによって数種類のモーダルを異なる順番で表示したい、という要件もあるかと思います。
本項では ES2017 の async/await を使ってQueueing を実装する方法を紹介します。vanilla なので、どのフレームワークやアーキテクチャを採用していても導入出来ます。
sample: https://github.com/takefumi-yoshii/async-await-modal-queue
サンプルは chrome で、babel や webpack を使用せずにそのまま実行出来ます。もちろんプロダクトコードは適宜バンドラや polyfill を利用してください。
モーダル画面を一つのPromiseと捉える
Promise といえば、XHR で json を取得したり、アニメーションの完了を待ったりする非同期処理のためのもの、というイメージが強いかと思います。「モーダルが表示されてから閉じるまで」などのUIの一連の操作も、期待値を待っている非同期処理と同等と捉えることが出来ます。
モーダル画面を表示する際の関数に、Promiseを発行させます。そして「閉じるボタン押下、背景押下」などの閉じる相当の行為で、発行された Promise を resolve する様に仕込んでおきます。
export function clickModalCloseButton () {
return (() => new Promise(resolve => {
document.getElementById('modal-close-button')
.addEventListener('click', resolve, { once: true })
})
)
}
async/await で Queueing
async function に無限ループを置きます。Queue配列に格納された Promise をひとつずつ取り出して await します。Queueが空になったら、無限ループを break。async function 自体の戻り値も Promise なので、クロージャの末尾まで到達・例外throw で resolve・reject となります。
let queue = []
async function playQueue() {
while (true) {
const { resolvedModalPromise, value } = queue[0]
await openModalAnimation(value)
await resolvedModalPromise()
await closeModalAnimation()
queue.shift()
if (queue.length === 0) break
}
}
Queueの再生
今度は別の async function で、
Queueに溜まったモーダルを再生させるイベントを待ちます。ボタン押下イベントを待つものもまた Promise として扱い、ループの中で await します。
export function clickPlayButton () {
return new Promise(resolve => {
document.getElementById('play-button')
.addEventListener('click', resolve, { once: true })
})
}
「Queueの再生が全て終わったら、また再生ボタン押下を待つ」というループですね。
export async function watchPlay() {
while (true) {
await clickPlayButton() // Promise
await playQueue() // async function
}
}
Redux や Flux では、特定の Action を待つ Promise を発行すれば良いですね。redux-saga や vuex-saga を導入していれば、async/await と同じ様に saga はデザインされているので、そちらに組み込みましょう。サンプルは分かり易さをとったので、jQueryライクな書き方を随所にしていますが、ご承知下さい。