Javaでpromiseを実装したす

すべおの良い䞀日。 今日は、JS゚ンゞンのpromiseメカニズムの実装をどのように曞いたかに぀いおお話したいず思いたす。 ご存知のように、新しいECMA Script 6暙準はそれほど前にリリヌスされおおらず、promiseの抂念は非垞に興味深く、たたWeb開発者によっお䜿甚される倚くの堎所に芋えたす。 したがっお、珟代のJS゚ンゞンでは、これは確かに必芁なものです。
泚意この蚘事には倚くのコヌドがありたす。 プロゞェクト党䜓が1人で䜜成され、ただベヌタ版であるため、コヌドは矎しく高品質であるように芋せかけおいたせん。 この物語の目的は、すべおが内郚でどのように機胜するかを瀺すこずです。 さらに、少し適合させた埌、このコヌドを䜿甚しお、JavaScriptに関係なく、玔粋にJavaでプロゞェクトを䜜成できたす。

コヌドを曞き始める最初のこずは、すべおが最終的にどのように機胜するかを孊ぶこずです。 結果のモゞュヌルのアヌキテクチャは、プロセス䞭に倧きく決定されたした。

Promiseずは䜕ですか


Promiseは特別なオブゞェクトであり、䜜成されるず保留状態になりたす0に等しい定数にしたす。

次に、オブゞェクトは、䜜成時にコンストラクタヌに枡された関数の実行を開始したす。 関数が枡されなかった堎合-ES6暙準に埓っお、 匕数をスロヌする必芁があるのは関数の䟋倖ではありたせん 。 ただし、Java実装では、䜕も投げるこずができず、オブゞェクトを「珟状のたた」䜜成するこずができたすその埌、远加のロゞックを远加したす。これに぀いおは埌で説明したす。

したがっお、コンストラクタヌは関数を受け入れたす。 ゚ンゞンでは、これはcallメ゜ッドを実装するFunctionクラスのオブゞェクトです。 このメ゜ッドを䜿甚するず、実行コンテキスト、匕数を持぀ベクトル、および呌び出しモヌドコンストラクタヌたたは通垞モヌドずしおの呌び出しを定矩するブヌルパラメヌタヌを䜿甚しお、関数を呌び出すこずができたす。

さらに、この関数はオブゞェクトのフィヌルドに曞き蟌たれ、呌び出すこずができたす。

public static int PENDING = 0; public static int FULFILLED = 1; public static int REJECTED = 2; ... private int state = 0; private Function func; 

同時に、ここでは、残りの2぀の状態の定数ず、オブゞェクトの珟圚の状態を栌玍するintフィヌルドを䜜成したす。

そのため、暙準に埓っお、実行䞭の関数は2぀の関数のいずれかを呌び出すこずができたす最初の2぀の匕数ずしお枡されるため、適切な方法で関数シグネチャで名前を指定する必芁がありたす。 通垞、簡単にするために解決や拒吊などの方法を䜿甚したす。

これらはJavaScriptの芳点からは通垞の関数です。぀たり、Functionオブゞェクトぱンゞンの芳点からのものです。 それらにもフィヌルドを远加したす。

  public Function onFulfilled = null; public Function onRejected = null; 

これらの関数は、メむンの䜜業関数によっおい぀でも呌び出すこずができたす。぀たり、そのスコヌプ内になければなりたせん。 さらに、䜜業を終えるず、オブゞェクトの状態をそれぞれ実珟および拒吊に倉曎する必芁がありたす。 私たちの関数はプロミスに぀いお䜕も知りたせん知っおはいけたせん。 したがっお、それらに぀いお認識し、状態の倉曎を開始できる䞀皮のラッパヌを䜜成する必芁がありたす。

たた、オブゞェクトにsetStateメ゜ッドが必芁です远加のチェックが必芁です。たずえば、状態が既に満たされおいるか拒吊されおいる堎合、状態を倉曎する暩利はありたせん。

オブゞェクトのコンストラクタを扱いたしょう

  public Promise(Function f) { func = f; onFulfilled = new PromiseHandleWrapper(this, null, Promise.FULFILLED); onRejected = new PromiseHandleWrapper(this, null, Promise.REJECTED); if (f != null) { Vector<JSValue> args = new Vector<JSValue>(); args.add(onFulfilled); args.add(onRejected); func.call(null, args, false); } } 

ここではすべおが明らかなようです。 関数が枡された堎合、すぐに呌び出す必芁がありたす。 そうでない堎合は、ただ䜕もしおいたせんそしお、オブゞェクトは保留状態を保持しおいたす。

次に、これらのハンドラヌ自䜓をむンストヌルするこずに぀いお説明したす結局、メむン関数では、名前を正匏なパラメヌタヌずしお宣蚀するだけです。 この暙準には、Promise.then解決、拒吊、Promise.then解決Promise.then解決、nullず同等、Promise.catch拒吊Promise.thennull、rejectず同等の3぀のオプションが甚意されおいたす。

then関数に関しおは、2぀の匕数を詳现に䜿甚しおメ゜ッドを実装し、残りの2぀を「ショヌトカット」ずしお䜜成するこずが最善であるこずは明らかです。 だから私たちはやる

  public Promise then(Function f1, Function f2) { if (state == Promise.FULFILLED || state == Promise.REJECTED) { onFulfilled = new PromiseHandleWrapper(this, f1, Promise.FULFILLED); onRejected = new PromiseHandleWrapper(this, f2, Promise.REJECTED); onFulfilled.call(null, new Vector<JSValue>(), false); return this; } ... onFulfilled = new PromiseHandleWrapper(this, f1, Promise.FULFILLED); onRejected = new PromiseHandleWrapper(this, f2, Promise.REJECTED); if (func != null) { String name1 = func.getParamsCount() > 0 ? func.getParamName(0) : "resolve"; String name2 = func.getParamsCount() > 1 ? func.getParamName(1) : "reject"; func.injectVar(name1, onFulfilled); func.injectVar(name2, onRejected); } if (f1 != null) has_handler = true; if (f2 != null) has_error_handler = true; return this; } 

最埌に、リンクを自分自身に返したす。これは、将来の有望なチェヌンの実装に必芁です。

メ゜ッドの最初にどのブロックがありたすか ただし、実際にハンドラを実行できるのは、それから最初に呌び出した前でもありたすこれは起こりたすが、これは完党に正垞です。 この堎合、すぐにメ゜ッドに枡されたハンドラヌから目的のハンドラヌを呌び出す必芁がありたす。

省略蚘号の代わりに、もう少し埌に別のコヌドがありたす。

次は、必須フィヌルドぞのハンドラヌのむンストヌルです。

そしお、ここが最も興味深い郚分です。 䜜業関数の実行に時間がかかるず仮定したすネットワヌク経由のリク゚スト、たたはケヌススタディの堎合は単にsetTimeout。 この堎合、基本的には実行されたすが、埌でコヌドを実行する倚数のオブゞェクトタむマヌ、ネットワヌクXmlHttpRequestむンタヌフェむスなどが䜜成されたす。 そしお、これらのオブゞェクトは関数のスコヌプにアクセスできたす

したがっお、必芁な倉数をスコヌプに远加するのにただ手遅れではないかもしれたせん手遅れの堎合、コヌドはメ゜ッドの先頭で実行されたす。 これを行うには、Functionクラスで新しいメ゜ッドを䜜成したす。

  public void injectVar(String name, JSValue value) { body.scope.put(name, value); } public void removeVar(String name) { body.scope.remove(name); } 

実際には、2番目の方法は必芁ありたせん。完党性のために䜜成されたものです。

次は、ショヌトカットを実装するずきです。

  public Promise then(Function f) { return then(f, null); } public Promise _catch(Function f) { return then(null, f); } 

catchはjavaの予玄語であるため、アンダヌスコアを远加する必芁がありたした。

次に、setStateメ゜ッドに぀いお説明したす。 最初の近䌌では、次のようになりたす。

  public void setState(int value) { if (this.state > 0) return; this.state = value; } 

さお、ハンドラヌから、より正確には、その䞊のラッパヌから状態を倉曎できたす。 ラッパヌをやっおみたしょう

 public class PromiseHandleWrapper extends Function { public PromiseHandleWrapper(Promise p, Function func, int type) { this.promise = p; this.func = func; this.to_state = type; } @Override public JSValue call(JSObject context, Vector<JSValue> args, boolean as_constr) { return call(context, args); } @Override public JSValue call(JSObject context, Vector<JSValue> args) { JSValue result; if (func != null) { Block b = getCaller(); if (b == null) { b = func.getParentBlock(); while (b.parent_block != null) { b = b.parent_block; } } func.setCaller(b); result = func.call(context, args, false); } else { result = Undefined.getInstance(); } promise.setResult(result); promise.setState(to_state); return promise.getResult(); } @Override public JSError getError() { return func.getError(); } private Promise promise; private Function func; private int to_state = 0; } 

ラッパヌには2぀のタむプがありたすが、クラスは1぀です。 たた、敎数フィヌルドto_stateがタむプを担圓したす。 それは悪くないようです:)

ラッパヌには、その機胜ずその玄束の䞡方ぞのリンクがありたす。 これは非垞に重芁です。

コンストラクタヌですべおが明確になったので、関数クラスのメ゜ッドをオヌバヌラむドする呌び出しメ゜ッドを芋おみたしょう。 JSむンタヌプリタヌの堎合、ラッパヌには同じ関数がありたす。぀たり、呌び出したり、倀を取埗したりできる、同じむンタヌフェヌスを持぀オブゞェクトです。

たず、ラッパヌが関数に呌び出されたずきに受け取ったCallerオブゞェクトをスロヌする必芁がありたす。これは、少なくずも正しい䟋倖がポップアップするために必芁です。

次に、関数を呌び出し、その実行結果をフィヌルドに保存したす。 同時に、promisオブゞェクトに蚭定し、そこに別のsetResultメ゜ッドを䜜成したす

  public JSValue getResult() { return result; } public void setResult(JSValue value) { result = value; } 

最埌の行に぀いおはただ説明したせん。これは連鎖に必芁です。 最も些现なケヌスでは、受信および送信した倀ず同じ倀がそこに返されたす。

重芁な点䜜業関数は、thenたたはcatchメ゜ッドを呌び出す前に、resolveたたはrejectを呌び出すこずができたすたたは、たったく呌び出さない堎合がありたす。 䟋倖を持たないように、プロミスを䜜成するずきに、ハンドラヌ関数を持たない2぀の「デフォルト」ラッパヌを䜜成したす。 呌び出されたずき、圌らは私たちの玄束の状態を倉曎するだけですそしお呌び出されたずき、これは考慮されたす。

远いかける玄束


芁するに、远跡はp.thenf1、f2.thenf3、f4.catchf5のようなものを曞く胜力です。
そのため、thenメ゜ッドず_catchメ゜ッドはPromiseオブゞェクトを返したす。

暙準が最初に䌝えるこずは、thenメ゜ッドは、既存のハンドラヌがある堎合、新しいプロミスを䜜成しおチェヌンに远加する必芁があるずいうこずです。 玄束は互いに等しくなければならないので、線圢リストを保存する䞻芁な玄束を持たないようにしたす。各玄束は、以䞋ぞのリンクのみを保存したす最初はnullです。

  public Promise then(Function f1, Function f2) { if (state == Promise.FULFILLED || state == Promise.REJECTED) { onFulfilled = new PromiseHandleWrapper(this, f1, Promise.FULFILLED); onRejected = new PromiseHandleWrapper(this, f2, Promise.REJECTED); onFulfilled.call(null, new Vector<JSValue>(), false); return this; } if (has_handler || has_error_handler) { if (next != null) { return next.then(f1, f2); } Promise p = new Promise(null); p.then(f1, f2); next = p; return p; } onFulfilled = new PromiseHandleWrapper(this, f1, Promise.FULFILLED); onRejected = new PromiseHandleWrapper(this, f2, Promise.REJECTED); if (func != null) { String name1 = func.getParamsCount() > 0 ? func.getParamName(0) : "resolve"; String name2 = func.getParamsCount() > 1 ? func.getParamName(1) : "reject"; func.injectVar(name1, onFulfilled); func.injectVar(name2, onRejected); } if (f1 != null) has_handler = true; if (f1 != null) has_error_handler = true; return this; } ... private Promise next = null; 

䞍足しおいるブロックは次のずおりです。すでに次の玄束がある堎合、呌び出しを圌に転送しお終了したす必芁に応じお、圌は最埌たで転送したす。 そしお、存圚しない堎合は、メ゜ッドで受け取ったハンドラヌを䜜成しお割り圓お、その埌、それを既に返したす。 すべおがシンプルです。

次に、setStateメ゜ッドを終了したす。

  public void setState(int value) { if (this.state > 0) return; this.state = value; Vector<JSValue> args = new Vector<JSValue>(); if (result != null) args.add(result); if (value == Promise.FULFILLED && next != null) { if (onFulfilled.getError() == null) { if (result != null && result instanceof Promise) { ((Promise)result).then(next.onFulfilled, next.onRejected); next = (Promise)result; } else { result = next.onFulfilled.call(null, args, false); } } else { args = new Vector<JSValue>(); args.add(onFulfilled.getError().getValue()); result = next.onRejected.call(null, args, false); } } if (value == Promise.REJECTED && !has_error_handler && next != null) { result = next.onRejected.call(null, args, false); } } 

たず、暙準では、前の結果を次の玄束のプロセッサに転送するこずが矩務付けられおいたすチェヌンの䞻な意味は、操䜜を割り圓おおから、2番目の操䜜を割り圓おお、最初の結果を受け入れるようにするこずです。

第二に、゚ラヌは特別な方法で凊理されたす。 成功した結果がチェヌンに沿っお倉曎しお最埌に送信されるず、ハンドラヌコヌドで発生した゚ラヌは、次の拒吊されるたで1ステップだけ送信されるか、チェヌンの最埌に到達するずポップアップしたす。

第䞉に、関数は新しいプロミスを返すこずができたす。 この堎合、次のものが既に蚭定されおいる堎合、既存のハンドラをスロヌするこずにより、次のものを眮き換える必芁がありたす。 これにより、むンスタント実行ハンドラヌず非同期ハンドラヌの組み合わせが可胜になり、それら自䜓がPromiseを返したす。

䞊蚘のコヌドは、これらすべおのシナリオに察応しおいたす。

最初のテスト


  JSParser jp = new JSParser("function cbk(str) { \"Promise fulfilled: \" + str } function f(resolve, reject) { setTimeout(function() { resolve(\"OK\") }, 1500) }"); System.out.println(); System.out.println("function cbk(str) { \"Promise fulfilled: \" + str }"); System.out.println("function f(resolve, reject) { setTimeout(function() { resolve(\"OK\") }, 1500) }"); System.out.println(); Expression exp = Expression.create(jp.getHead()); exp.eval(); jsparser.Function f = (jsparser.Function)Expression.getVar("f", exp); f.setSilent(true); jsparser.Promise p = new jsparser.Promise(f); p.then((jsparser.Function)Expression.getVar("cbk", exp)); 

これたで、すべおをJavaコヌドで管理しおきたした。 それにもかかわらず、すべおがすでに機胜しおいたす。1秒半で、コン゜ヌルに「玄束が満たされたしたOK」ずいう碑文が衚瀺されたす。 ずころで、Promiseのワヌキング関数からチェヌンなしで呌び出される解決関数ず拒吊関数は、任意の数の匕数を取るこずができたす。 ずおも䟿利です。 この䟋では、文字列「OK」を枡したした。

別の小さなコメントチェヌン䞭に䜜成されたプロミスには、原則ずしお機胜する機胜はありたせん。 前のプロミスの状態が倉化するず、すぐにハンドラヌを呌び出したす。

䟋はもっず耇雑です

  JSParser jp = new JSParser("function cbk1(str) { \"Promise 1 fulfilled: \" + str; return str } " + "function cbk2(str) { setTimeout(str => { \"Promise 2 fulfilled: \" + str }, 1000); throw \"ERROR\" } " + "function cbk3(str) { \"Promise 3 fulfilled: \" + str; return str } " + "function err(str) { \"An error has occured: \" + str } " + "function f(resolve, reject) { setTimeout(function() { resolve(\"OK\") }, 300) }"); System.out.println(); System.out.println("function cbk1(str) { \"Promise 1 fulfilled: \" + str; return str }"); System.out.println("function cbk2(str) { setTimeout(str => { \"Promise 2 fulfilled: \" + str }, 1000); throw \"ERROR\" }"); System.out.println("function cbk3(str) { \"Promise 3 fulfilled: \" + str; return str }"); System.out.println("function err(str) { \"An error has occured: \" + str }"); System.out.println("function f(resolve, reject) { setTimeout(function() { resolve(\"OK\") }, 300) }"); System.out.println("(new Promise(f)).then(cbk1).then(cbk2).then(cbk3, err)"); System.out.println(); Expression exp = Expression.create(jp.getHead()); ((jsparser.Function)Expression.getVar("f", exp)).setSilent(true); ((jsparser.Function)Expression.getVar("cbk2", exp)).setSilent(true); exp.eval(); jsparser.Function f = (jsparser.Function)Expression.getVar("f", exp); f.setSilent(true); jsparser.Promise p = new jsparser.Promise(f); p.then((jsparser.Function)Expression.getVar("cbk1", exp)) .then((jsparser.Function)Expression.getVar("cbk2", exp)) .then((jsparser.Function)Expression.getVar("cbk3", exp), (jsparser.Function)Expression.getVar("err", exp)); 

この䟋を呌び出すず、次の出力が埗られたす。

{}
"Promise 1 fulfilled: OK"
"OK"
"An error has occured: ERROR"
undefined
"Promise 2 fulfilled: OK"

最初の䞭括匧は、チェヌンの結果ずしおコヌルのチェヌンが返されたpromiseオブゞェクトです。 cbk1関数では、「OK」を返したした。この倀はcbk2に枡され、最埌の行に衚瀺されおいたす。 cbk2の内郚では、倀「ERROR」の゚ラヌをスロヌしたす。したがっお、cbk3は実行されたせんが、errが実行されたすチェヌン内の前のpromiseのハンドラヌで゚ラヌが発生した堎合。 しかし、このコヌドは即座に実行されたすが、cbk2の出力はタむマヌに掛けられた補助関数を介しお行われたす。 必芁に応じおstr倉数にアクセスできたすが、出力は以䞋のずおりです。 この䟋をChrome 49で実行するず、1぀の䟋倖を陀いおたったく同じ出力が埗られたす。倉数strは、setTimeoutに枡された匿名関数では衚瀺されたせん。 これは、Chromeの矢印関数の動䜜の特城ですそしお、おそらく暙準に埓っお非垞に必芁です。ここでは、問題が䜕であるかを蚀うのが難しいず思いたす。 矢印関数を通垞の関数に倉曎するず、出力は同じになりたす。

Javascript probros


しかし、それだけではありたせん。 私たちの最終的な目暙は、むンタヌプリタヌによっお実行されるJSコヌドが新しい機胜を䜿甚するこずです。 しかし、これはすでに技術の問題です。

コンストラクタヌを䜜成したす。

 public class PromiseC extends Function { public PromiseC() { items.put("prototype", PromiseProto.getInstance()); PromiseProto.getInstance().set("constructor", this); } @Override public JSValue call(JSObject context, Vector<JSValue> args, boolean as_constr) { return call(context, args); } @Override public JSValue call(JSObject context, Vector<JSValue> args) { if (args.size() == 0) return new Promise(null); if (!args.get(0).getType().equals("Function")) { JSError e = new JSError(null, "Type error: argument is not a function", getCaller().getStack()); getCaller().error = e; return new Promise(null); } return new Promise((Function)args.get(0)); } } 

そしお、必芁なメ゜ッドのセットを持぀プロトタむプオブゞェクト

 public class PromiseProto extends JSObject { class thenFunction extends Function { @Override public JSValue call(JSObject context, Vector<JSValue> args, boolean as_constr) { if (args.size() == 1 && args.get(0).getType().equals("Function")) { return ((Promise)context).then((Function)args.get(0)); } else if (args.size() > 1 && args.get(0).getType().equals("Function") && args.get(1).getType().equals("Function")) { return ((Promise)context).then((Function)args.get(0), (Function)args.get(1)); } else if (args.size() > 1 && args.get(0).getType().equals("null") && args.get(1).getType().equals("Function")) { return ((Promise)context)._catch((Function)args.get(1)); } return context; } } class catchFunction extends Function { @Override public JSValue call(JSObject context, Vector<JSValue> args, boolean as_constr) { if (args.size() > 0 && args.get(0).getType().equals("Function")) { return ((Promise)context)._catch((Function)args.get(0)); } return context; } } private PromiseProto() { items.put("then", new thenFunction()); items.put("catch", new catchFunction()); } public static PromiseProto getInstance() { if (instance == null) { instance = new PromiseProto(); } return instance; } @Override public void set(JSString str, JSValue value) { set(str.getValue(), value); } @Override public void set(String str, JSValue value) { if (str.equals("constructor")) { super.set(str, value); } } @Override public String toString() { String result = ""; Set keys = items.keySet(); Iterator it = keys.iterator(); while (it.hasNext()) { if (result.length() > 0) result += ", "; String str = (String)it.next(); result += str + ": " + items.get(str).toString(); } return "{" + result + "}"; } @Override public String getType() { return type; } private String type = "Object"; private static PromiseProto instance = null; } 

すべおが機胜するように、最初にPromiseコンストラクタヌに1行远加するこずを忘れないでください。

  public Promise(Function f) { items.put("__proto__", PromiseProto.getInstance()); ... } 

そしお、テストを少し倉曎したす。

  JSParser jp = new JSParser("function cbk1(str) { \"Promise 1 fulfilled: \" + str; return str } " + "function cbk2(str) { setTimeout(str => { \"Promise 2 fulfilled: \" + str }, 1000); throw \"ERROR\" } " + "function cbk3(str) { \"Promise 3 fulfilled: \" + str; return str } " + "function err(str) { \"An error has occured: \" + str } " + "function f(resolve, reject) { setTimeout(function() { resolve(\"OK\") }, 300) }; " + "(new Promise(f)).then(cbk1).then(cbk2).then(cbk3, err)"); System.out.println(); System.out.println("function cbk1(str) { \"Promise 1 fulfilled: \" + str; return str }"); System.out.println("function cbk2(str) { setTimeout(str => { \"Promise 2 fulfilled: \" + str }, 1000); throw \"ERROR\" }"); System.out.println("function cbk3(str) { \"Promise 3 fulfilled: \" + str; return str }"); System.out.println("function err(str) { \"An error has occured: \" + str }"); System.out.println("function f(resolve, reject) { setTimeout(function() { resolve(\"OK\") }, 300) }"); System.out.println("(new Promise(f)).then(cbk1).then(cbk2).then(cbk3, err)"); System.out.println(); Expression exp = Expression.create(jp.getHead()); ((jsparser.Function)Expression.getVar("f", exp)).setSilent(true); ((jsparser.Function)Expression.getVar("cbk2", exp)).setSilent(true); exp.eval(); jsparser.Function f = (jsparser.Function)Expression.getVar("f", exp); f.setSilent(true); 

結論は倉わらないはずです。

それだけです すべおが正垞に機胜し、远加の単䜓テストを蚘述しお、発生する可胜性のある゚ラヌを怜玢できたす。

このメカニズムをJavaに適応させる方法は ずおも簡単です。 関数に䌌たクラスを䜜成し、operateメ゜ッドで䜕かを行いたす。 そしお、すでにラッパヌでラップしおいたす。 いずれにせよ、このテヌマにはたくさんの玠晎らしいパタヌンがありたす。

この蚘事が誰かに圹立぀こずを願っおいたす。 ゚ンゞン゜ヌスを思い぀いたらすぐに投皿し、䞍足しおいる機胜を远加したす。 良い䞀日を

Source: https://habr.com/ru/post/J350444/


All Articles