Skip to content

Instantly share code, notes, and snippets.

@mike-neck
Last active February 24, 2021 23:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mike-neck/f82d2887b87e5c0b1d536e581931144c to your computer and use it in GitHub Desktop.
Save mike-neck/f82d2887b87e5c0b1d536e581931144c to your computer and use it in GitHub Desktop.
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}`,
});
}
});
}
@mike-neck
Copy link
Author

https://twitter.com/wonderful_panda/status/1364718662016397313?s=20

resolve({error: "...", cause: "..."}) のあたりは Promise.resolve({error: "...", cause: "..."}) でもいけるらしい

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment