Last active
February 24, 2021 23:36
-
-
Save mike-neck/f82d2887b87e5c0b1d536e581931144c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {Context} from "./Context"; // API のリクエスト先が格納されているオブジェクト | |
import * as pako from "pako"; // zlib を使えるようにするライブラリー | |
// javap サービスへのリクエストが成功して返ってきた javap の結果(複数ある)が格納されるオブジェクトの型 | |
export type JavapSuccess = { | |
contents: JavapOutput[], | |
}; | |
// javap の結果 ファイル名とその内容 | |
export type JavapOutput = { | |
name: string, | |
outputs: string, | |
}; | |
// javap サービスへのリクエストが失敗したことをあらわすオブジェクトの型 | |
export type JavapError = { | |
error: string, | |
cause: string, | |
}; | |
// 他のファイルがこのファイルを利用するときに見えるインターフェース | |
export type JavapService = { | |
call: (javaCode: string) => Promise<JavapSuccess | JavapError> | |
}; | |
// 他のファイルが JavapSuccess | JavapError の型アサーションによって型を分類するための関数(Scala の match 式) | |
export function isJavapSuccess(some: JavapSuccess | JavapError): some is JavapSuccess { | |
return "contents" in some; | |
} | |
// 他のファイルが JavapSuccess | JavapError の型アサーションによって型を分類するための関数(Scala の match 式) | |
export function isJavapError(some: JavapSuccess | JavapError): some is JavapError { | |
return "error" in some && "cause" in some; | |
} | |
// 他のファイルが Context から新しい JavapService を作り出すためのファクトリー関数 | |
export function newJavapService(context: Context): JavapService { | |
return { | |
call(javaCode: string): Promise<JavapSuccess | JavapError> { | |
// 実装は callApi 関数に移譲(callApi の内容を書いていたけど、読みづらくなったので移動した) | |
return callApi(context, javaCode) | |
// 想定外のエラーについてもできる限り Promise のエラーで返したくなかったので(特にネットワーク系エラーについて)エラーをマッピングする) | |
.catch(reason => typeErrorToJavapError(reason)); | |
} | |
}; | |
} | |
// javap-server の API を呼び出す関数 | |
// ネットワークアクセスが発生するので、時間がかかる + エラーになることから Promise を返す | |
// Promise を返す関数の中で Promise を返す関数を呼び出すので、簡単にするために async 関数であると宣言している | |
// async を指定していない関数の中では await で値だけの受け取りはできない | |
async function callApi(context: Context, javaCode: string): Promise<JavapSuccess | JavapError> { | |
// 計算が失敗する可能性がある関数を呼び出して、成功した場合のみを扱うので await キーワードを指定して中身だけ受け取る | |
// Promise<string> が返されるが string で受け取る | |
const base64 = await deflateToBase64(javaCode); | |
const url = `${context.apiUrl}/${base64}`; | |
// fetch は Promise を返すので、成功した場合のみ扱いたいから await キーワードを指定して受け取る | |
const response = await fetch(url, { | |
method: "GET", | |
mode: "cors", | |
headers: { | |
"accept": "application/json", | |
} | |
}); | |
// fetch が返す Response の json 関数はパースに失敗する事があるので Promise<any> を返す | |
// Promise<any> を any で扱いたいから await キーワードをつけて受け取る | |
const object = await response.json(); | |
if (response.status === 200 && "contents" in object) { | |
return { | |
contents: object["contents"] | |
}; | |
} else if (response.status === 200) { | |
return { | |
cause: "server returns success but no contents found", | |
error: "error, try again", | |
}; | |
} | |
const error = extractError(object); | |
const cause = extractCause(object); | |
return { | |
cause: cause, | |
error: error, | |
}; | |
} | |
// ネットワークアクセスなどは発生しないため、 Promise<string> にする必要はないが、エラーが発生するので Promise<string> にしている | |
async function deflateToBase64(javaCode: string): Promise<string> { | |
const textEncoder = new TextEncoder(); | |
const bytes = pako.gzip(textEncoder.encode(javaCode)); | |
// @ts-ignore | |
const byteString = String.fromCharCode(...bytes); | |
return btoa(byteString) | |
.replaceAll('/', '_') | |
.replaceAll('+', '-'); | |
} | |
function extractError(object: any): string { | |
if ("error" in object) return object["error"]; | |
return "error but no detail available"; | |
} | |
function extractCause(object: any): string { | |
if ("cause" in object) return object["cause"]; | |
return "unknown error"; | |
} | |
// 特に待機するものがないマッピングだけの関数なので、 Promise<JavapSuccess | JavapError> を返す型になっているが、 async にしていない | |
// async を指定していない関数の中では await で値だけの受け取りはできない | |
function typeErrorToJavapError(e: any): Promise<JavapSuccess | JavapError> { | |
// 新しい Promise を返す場合は new でコンストラクターの中に (resolve(Successのオブジェクト), reject(エラー)) を受け取り void を返す関数を渡す | |
return new Promise<JavapSuccess | JavapError>(resolve => { | |
if ("message" in e) { | |
resolve({ | |
error: e["message"], | |
cause: e["message"], | |
}); | |
} else { | |
// 原因のわからないエラーについてまでも JavapError にマッピングするのはオーバーエンジニアリングな印象はある | |
// ただし、後々の画面処理を簡単にするためにやっている(サービスが画面のことを慮るのはオーバーエンジニアリング) | |
resolve({ | |
error: `error: ${e}`, | |
cause: `${e}`, | |
}); | |
} | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://twitter.com/wonderful_panda/status/1364718662016397313?s=20
resolve({error: "...", cause: "..."})
のあたりはPromise.resolve({error: "...", cause: "..."})
でもいけるらしい