以下の内容はhttps://let.blog.jp/tag/asyncより取得しました。


useMemo と Promise
state から計算できる値で毎回計算したくないのを useMemo でメモ化していました
あるとき重めの処理の中に async 関数が入り await が必要になりました
レンダリング処理の途中に await は書けないです
await しないと useMemo の結果は Promise です

const promise = useMemo(() => {
return fn(foo, bar)
}, [fn, foo, bar])

こんなことを考えてみましたが React ではプロパティが途中で変わっても再レンダリングされなければ画面に影響しません

const value = useMemo(() => {
const result = {
value: null,
}
fn(foo, bar).then(result_value => {
result.value = result_value
})
return result
}, [fn, foo, bar])

resolve されたタイミングで再レンダリングさせるには state を更新するしかないです
setState するなら useEffect の中なのでこんな感じになります

useEffect(() => {
const promise = fn(foo, bar)
setState(null)
promise.then(result => {
setState(result)
})
}, [fn, foo, bar])

できれば避けたいやつです
毎回そういう事するのは避けたいのでフック化してこういう感じです

const usePromise = (promise) => {
const [value, setValue] = useState(null)
promise.then(result => setValue(result))
return value
}

const value = usePromise(promise)

ただこれだけだと promise が変わったときにリセットできないのでやはり useEffect は必要です
また何度も then で関数を設定することになるので 1 回だけで済むように useEffect の中で行うようにします

const usePromise = (promise) => {
const [value, setValue] = useState(null)
useEffect(() => {
setValue(null)
let alive = true
promise.then(result => {
alive && setValue(result)
})
return () => {
alive = false
}
}, [promise])
return value
}

const value = usePromise(promise)

引数の Promise は state に入れたり useMemo を使って同じ Promise を渡すようにしないと無限ループになります

const value = usePromise(Promise.resolve(1))

だと then の中で setValue(1) が実行されて再レンダリングされるたびに Promise が新しくなり また useEffect 内で then がセットされてのループです
ちょっとした使い方のミスで無限ループするのは扱いづらいのかもしれないです
fs.appendFile は順番保証されなかった
書き込みを待たずに次の処理に行きたかったのでこんなのを書いたら

const fs = require("fs")

const w = text => {
fs.appendFile("./log.txt", text + "\n", () => {})
}

w("A")
w("B")
w("C")
w("D")
w("E")
w("F")
w("G")
w("H")
w("I")
w("J")

順番がバラバラだった

D
A
C
E
F
H
B
I
G
J

非同期とは言え書き込み順は保証されてると思ったのにそんなことなかった

キューに入れて書き込み順は保証するようにした

const fs = require("fs")

const w = (() => {
const q = []
let running = false
const run = async () => {
running = true
let text
while(text = q.shift()) {
await fs.promises.appendFile("./log.txt", text + "\n")
}
running = false
}
return text => {
q.push(text)
if (!running) run()
}
})()

w("A")
w("B")
w("C")
w("D")
w("E")
w("F")
w("G")
w("H")
w("I")
w("J")
async 対応なコレクションメソッドがほしい
map は普通に map に渡す関数を async にしたら Promise の配列になるので Promise.all するだけ

const items = [10, 20, 30]
const asyncDiv5 = num => new Promise(resolve => setTimeout(resolve, 100, num / 5))

await Promise.all(items.map(asyncDiv5))
// [2, 4, 6]

reduce も返り値が Promise でも次の関数に渡されるのが Promise なだけだから次の関数が前回の値を await して使えばいい

const values = [1, 2, 3, 4]
const getValue = x => Promise.resolve(x * 10)

await values.reduce(async (a, b) => (await a) + (await getValue(b)), 0)
// 100

問題は渡した関数の返り値の値をコレクションメソッド内部で扱う場合
これらは Promise に対応してないので Promise を解決せずそのまま Promise 自体を結果として扱う
なので filter の場合だと Promise オブジェクトはオブジェクトなので true になって全部が残るし find でも最初のが絶対マッチする

console.log([1, 2].filter(async x => false))
// [1, 2]

console.log([1, 2].find(async x => false))
// 1

filter に対応するならこういう一度 Promise の中身を取り出す処理が必要になる

const filter = async (arr, fn) => {
const filter_flags = await Promise.all(items.map(fn))
return arr.filter((_, i) => filter_flags[i])
}

この処理を毎回直接書くのは避けたいくらいので filter 関数を自作することになる
標準で用意してくれればいいのに

const items = [0, 1, 2, 3, 4]
const fn = x => {
if(x === 0) return false
if(x === 1) return true
if(x === 2) return new Promise(r => setTimeout(r, 1000, true))
if(x === 3) return Promise.resolve(false)
if(x === 4) return Promise.resolve(true)
}

await filter(items, fn)
// [1, 2, 4]
async 関数で promise を返す
promise を promise で包むと勝手に展開される
なので async 関数が promise 返しても then で受け取れるのは promise じゃなくて promise の中の値

async function foo() {
const value = await something()
const promise = longProcess(value.bar)
return promise
}

async function main() {
const promise = await foo()
promise.then(() => {
console.log("LONG PROCESS END")
})
console.log("LONG PROCESS START")
}

main()

await foo() の結果の promise は longProcess が返す promise の中の値なので then はなくてエラー
オブジェクトに包めば展開されないので promise を返せる

async function foo() {
const value = await something()
const promise = longProcess(value.bar)
return { promise }
}

async function main() {
const { promise } = await foo()
promise.then(() => {
console.log("LONG PROCESS END")
})
console.log("LONG PROCESS START")
}

main()

⇩実行用に longProcess とかのサンプル実装

const wait = ms => new Promise(resolve => setTimeout(resolve, ms))
const something = async () => {
await wait(1000)
return { foo: 1, bar: 2 }
}
const longProcess = async () => {
await wait(5000)
}
非同期処理を順番にする書き方
// 1
async function case1() {
try{
await asyncAction1()
await asyncAction2()
await asyncAction3()
} catch (err) {
console.error(err)
}
}

// 2
function case2() {
return chain([
asyncAction1,
asyncAction2,
asyncAction3,
]).catch(console.error)
}

function chain(afns) {
return afns.reduce((a, b) => a.then(b), Promise.resolve())
}

// 3
function case3() {
return Promise.resolve()
.then(asyncAction1)
.then(asyncAction2)
.then(asyncAction3)
.catch(console.error)
}

1 は async/await な方法
各処理の間に何かやりたいときにやりやすい
動的に実行関数変えるなら配列にして for-of で回すだけ

2 は Promise をチェーンさせる関数を使ったもの
関数作る必要あるけど chain は汎用的なものだし 1 回つくれば使い回せるからそこまで気にならない
関数の配列渡すだけなのでシンプル

3 は自分でチェーンさせたもの
2 よりこれのほうが見やすいかもだけど 動的に項目変わると対応しづらい
配列だけ作って渡せばいい 2 のほうが扱いやすい
async の catch
非同期でエラーを起こす関数を用意
1 秒後に Promise を reject

async function causeAsyncError() {
return new Promise((ok, ng) => setTimeout(ng, 1000))
}

try の中で呼び出しても非同期なものは catch されない

async function f1() {
try {
causeAsyncError()
} catch (err) {
console.log("catch")
}
}

f1()
// uncaught error

返り値を await すると catch される

async function f2() {
try {
await causeAsyncError()
} catch (err) {
console.log("catch")
}
}

f2()
// catch

f1 を呼び出す側で await しても効果なし

try {
await f1()
} catch (err) {
cosnole.log("catch")
}
// uncaught error

f1 の Promise は causeAsyncError の結果を待たずに解決される
ループの中などで await させたくない場合は個々に catch メソッド使うことになる

async function f1_() {
try {
causeAsyncError().catch(e => console.log("catch"))
} catch (err) {
console.log("catch")
}
}

f1_()
// catcch

AsyncFunction がグローバルに用意されてない
Function("arg1", "arg2", "return arg1 + arg2")(10, 20)
// 30

みたいなことを async 版でやりたいとき
↓ を準備

window.AsyncFunction = (async()=>{}).constructor

あとは同じように使える

await AsyncFunction("arg1", "return await fetch(arg1).then(e => e.text())")(location.href)
// <!doctype html> ……




以上の内容はhttps://let.blog.jp/tag/asyncより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14