promise-java異步編程解決方案

java promise(GitHub)是Promise A+規範的java實現版本。Promise A+是commonJs規範提出的一種異步編程解決方案,比傳統的解決方案—回調函數和事件—更合理和更強大。promise實現了Promise A+規範,包裝了java中對多線程的操做,提供統一的接口,使得控制異步操做更加容易。實現過程當中參考文檔以下:java

基本使用:修改pom.xmlgit

<repositories>
    <repository>
      <id>wjj-maven-repo</id>
      <url>https://raw.github.com/zhanyingf15/maven-repo/master</url>
    </repository>
</repositories>
複製代碼
<dependency>
  <groupId>com.wjj</groupId>
  <artifactId>promise</artifactId>
  <version>1.0.0</version>
</dependency>
複製代碼

若是maven settings.xml使用了mirror配置,修改mirrorOfes6

<mirror>
  <id>nexus</id>
  <mirrorOf>*,!wjj-maven-repo</mirrorOf> 
  <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</mirror>
複製代碼
IPromise promise = new Promise.Builder().promiseHanler(new PromiseHandler() {
    @Override
    public Object run(PromiseExecutor executor) throws Exception {
        return 2*3;
    }
}).build();
複製代碼

上面的例子中建立了一個promise對象,指定PromiseHandler實現,在run方法中寫具體的業務邏輯,相似於Runable的run方法。promise對象一經建立,將當即異步執行。推薦使用lambda表達式,更加簡潔。github

IPromise promise = new Promise.Builder().promiseHanler(executor -> {
    return 2*3;
}).build();
複製代碼

獲取promise的執行結果一般使用兩個方法thenlisten,前者是阻塞的後者是非阻塞的。then方法返回一個新的promise對象,所以支持鏈式調用。編程

new Promise.Builder().promiseHanler(executor -> {//promise0
    return 2*3;
}).build().then(resolvedData -> {//返回一個新的promise1
    System.out.println(resolvedData);
    return (Integer)resolvedData+1;
}).then(res2->{
    System.out.println(res2);
    //建立一個新的promise2並返回
    return new Promise.Builder().externalInput(res2).promiseHanler(executor -> {
        return (Integer)executor.getExternalInput()+2;
    }).build();
}).then(res3->{
    System.out.println(res3);
    return res3;
});
複製代碼

從上面能夠看到promise0、promise1和Promise2是鏈式調用的,每一次then方法都返回一個新的promise。在then方法的回調中,若是返回的是一個非promise對象,那麼promise被認爲是一個fulfilled狀態的promise,若是返回的是一個promsie實例,那麼該實例將會異步執行。
假如須要異步順序執行a->b-c->d四個線程,調用順序以下數組

new PromiseA()
.then(dataA->new PromiseB())//A的回調
.then(dataB->new PromiseC())//B的回調
.then(dataC->new PromiseD())//C的回調
.then(dataD->xxx)//D的回調
.pCatch(error->xxxx)//捕獲中間可能產生的異常
複製代碼

DOCS

docs文檔參考promise wikipromise

promise規範

promise規範能夠參考 Promise A+規範。其中ES6 Promise對象 在Promise A+規範上作了一些補充。java promise在使用上基本與ES6 Promise對象保持一致,部分地方有些許不一樣。 Promise的三個狀態bash

  • pending:等待態,對應線程未執行或執行中
  • fulfilled:完成態,對應線程正常執行完畢,其執行結果稱爲終值
  • rejected:拒絕態,對應線程異常結束,其異常緣由稱爲拒因
    狀態轉移只能由pending->fulfilled或pending->rejected,狀態一旦發生轉移沒法再次改變。

使用

IPromise promise = new Promise.Builder().promiseHandler(handler->2*3).build();//mark1
promise.then(resolvedData -> {
    System.out.println(resolvedData);
    return null;
});
複製代碼

建立一個線程很是簡單,mark1標註的行建立一個IPromise實例promise,並指定異步邏輯,這裏簡單地作了個乘法操做。promise實例一經建立,異步邏輯將當即執行,執行結果或執行中拋出的異常將保存在promise實例中。能夠在建立promise實例時指定一個線程池。多線程

ExecutorService pool = Promise.pool(5,10);
IPromise promise = new Promise.Builder().pool(pool).promiseHandler(handler->2*3).build();
複製代碼

上面建立了一個最小爲5最大爲10的線程池,promise實例對應的線程將被提交的線程池中執行。promise能夠經過then或listen方法獲取執行結果,then方法是阻塞的而listen是非阻塞的。
一般狀況下異步邏輯須要訪問外部參數,而外部參數每每並非final的,promise提供了輸入外部參數到內部邏輯的方法externalInput異步

Map<String,String> map = ImmutableMap.of("name","張三");
IPromise promise = new Promise.Builder().externalInput(map).promiseHandler(handler->{
    Map<String,String> m = (Map<String,String>)handler.getExternalInput();
    return "你好:"+m.get("name");
}).build();
複製代碼

resolve和reject

resolve和reject是PromiseExecutor類的方法,resolve方法將promise狀態由pending->fulfilled,reject方法將promise狀態由pending->rejected。若是promise已是非pending狀態,resolve和reject調用將無效。

new Promise.Builder().promiseHandler(handler->{
    int a = 2*3;
    handler.resolve(a);
    return null;
}).build().listen(((resolvedData, e) -> {
    System.out.println(resolvedData);
}));
複製代碼

上面的例子中,計算出a的值,手動將promise狀態轉移爲fulfilled,並將a的值做爲promise的終值。一樣也能夠手動調用handler.reject(e)將promise狀態轉爲rejected,e(e爲Throwable實例)做爲promise的據因。

在前面和後面的例子中,並無顯示調用handler.resolve(x)方法,而是return具體的結果。由於在 resolve方法以後return以前程序拋出異常,該異常不會更改promise的狀態,異常會被內部吞掉,resolve方法已經將promise的狀態修改成fulfilled了。

new Promise.Builder().promiseHandler(handler->{
    int a = 2*3;
    handler.resolve(a);
    throw new RuntimeException("err");
}).build()
        .listen(((resolvedData, e) -> {
    System.out.println(resolvedData);
    System.out.println(e==null);
}));
複製代碼

打印結果

6
true
複製代碼

上面的例子中,手動調用resolve方法後,後續邏輯即使是拋出了異常,e仍然是null,由於promise狀態已經轉變爲fulfilled,後續的全部邏輯(包括return的值)已經跟promise的最終狀態無關,後續異常和返回結果將被忽略。所以非特殊狀況下不建議直接調用resolve方法,而是直接return返回執行結果。這種方式是handler.resolve(x)的隱式作法,return的結果將做爲promise的終值

異常捕獲

promise的rejected狀態對應線程異常結束(運行時異常或手動調用executor.reject(e)),其據因保存了異常實例,這些異常被promise內部吞掉,並不會拋出到當前運行環境中,全部try...catch是沒法捕獲到promise內部邏輯拋出的異常的。promise提供了多種方式能夠偵測到內部異常:

then方法的第二個參數是可選參數,若是發生異常,第二個參數回調將被執行。

listen和pFinally行爲是一致的,onCompleteListener的listen(Object resolvedData,Throwable e)方法第二參數爲異常對象,若是發生異常,e爲異常實例,不然爲null。

pCatch是推薦使用的異常捕獲方式,then和listen對於異常只能「觀察」不能修正,在promise鏈式調用時,一旦發生異常,then方法只能觀察到異常發生,可是異常仍會向調用鏈後方傳遞,並拒絕後面promise的執行。pCatch不一樣,它捕獲到異常後,能夠自行根據業務邏輯對異常處理,繼續執行後面的promise鏈。

new Promise.Builder().promiseHandler(executor -> 3).build().then(resolvedData->{//p1
    System.out.println("a:"+resolvedData);
    return new Promise.Builder().promiseHandler(executor -> {//p2
        executor.reject(new RuntimeException("err"));
        return resolvedData;
    }).build();
}).then(resolvedData1 -> {//p3
        System.out.println("b:"+resolvedData1);
        return "b:"+resolvedData1;
    },rejectReason -> {
        System.err.println("c:"+rejectReason);
    }
).then(resolvedData2 -> {//p4
        System.out.println("d:"+resolvedData2);
        return "d:"+resolvedData2;
    },rejectReason -> {
        System.err.println("e:"+rejectReason);
    }
);
複製代碼

執行結果

a:3
c:java.lang.RuntimeException: err
e:java.lang.RuntimeException: err
複製代碼

在上面的例子中,p1,p2,p3鏈式調用,p1執行後在p2處手動拋出異常,p2的then偵測到異常,p3,p4的正常邏輯被取消了

new Promise.Builder().promiseHandler(executor -> 3).build().then(resolvedData->{
    System.out.println("a:"+resolvedData);
    return new Promise.Builder().promiseHandler(executor -> {
        executor.reject(new RuntimeException("err"));
        return resolvedData;
    }).build();
}).pCatch(e->{
    System.out.println("捕獲到異常");
    return 3;
}).then(resolvedData1 -> {
        System.out.println("b:"+resolvedData1);
        return "b:"+resolvedData1;
    },rejectReason -> {
        System.err.println("c:"+rejectReason);
    }
).then(resolvedData2 -> {
        System.out.println("d:"+resolvedData2);
        return "d:"+resolvedData2;
    },rejectReason -> {
        System.err.println("e:"+rejectReason);
    }
);
複製代碼

打印結果

a:3
捕獲到異常
b:3
d:b:3
複製代碼

pCatch捕獲到異常後,返回一個修正值3,這個值會傳遞個下一個promise處理,繼續完成鏈式調用。pCatch也能夠直接返回一個promise,promise的狀態決定是否繼續後續鏈的執行(若是pCatch返回的promise是rejected狀態仍然會拒絕後續promise的執行直到遇到下一個pCatch)

pCatch能夠在promise鏈的任何位置出現,出現的次數不受限制,若是沒有異常出現,將忽略pCatch邏輯。listen和pFinally只能在鏈的末尾出現,不管異常是否發生,它都將被調用(相似於try...catch...finally)。

promise 組合

因爲開發中沒法預計線程何時執行結束,有時須要拿到線程執行結果在進行下一步操做就比較麻煩。若是是單個promise,能夠簡單地使用then方法阻塞當前線程,等待promise線程執行完畢。若是是多個promise並行執行,須要等待全部的promise都執行完畢才能執行下一步,可使用all或者waitAll方法。

IPromise p1 = new Promise.Builder().promiseHandler(executor -> {
    Thread.sleep(1000);
    return 1;
}).build();
IPromise p2 = new Promise.Builder().promiseHandler(executor -> {
    Thread.sleep(4000);
    return 2;
}).build();
IPromise p3 = new Promise.Builder().promiseHandler(executor -> {
    Thread.sleep(2000);
    return 3;
}).build();
Promise.all(p1,p2,p3).then(resolvedData -> {
    Object[] datas = (Object[])resolvedData;
    for(Object d:datas){
        System.out.println(d);
    }
    return null;
},e->e.printStackTrace());
複製代碼

上面建立了三個promise,Promise.all將三個promise組裝成一個新promise p,新的promise p的狀態將由p1-p3的狀態決定,若是p1-p3所有正常結束,p的狀態是fulfilled,其終值是一個數組,按傳入順序保存p1-p3的執行結果。依據promise規範,若是p1-p3任意一個異常結束或手動調用executor.reject()方法將pn狀態轉爲rejected,p的狀態會轉爲rejected,並嘗試取消其他promise。具體能夠參考wiki all

1.0.1版本all還有一個重載方法all(ExecutorService threadPool,final IPromise ...promises),能夠指定p的執行環境,不指定線程池默認新開一個線程。

在有些狀況下,當p1-p3的其中一個發生異常時,並不但願p的狀態當即轉變爲rejected並嘗試取消其他promise的執行,而是但願其他promise繼續執行,可使用waitAll()方法。Promise.waitAll將多個promise組裝成一個新promise p,不一樣於all,p1-p3的狀態不會影響p的狀態,若是p自身未發生異常(waitAll內部使用了CountDownLatch處理多個線程,可能會有異常),p的狀態一直是fulfilled,其終值是一個數組,數組值是pn的終值或據因。具體可參考wiki-waitAll,使用方式以下:

IPromise p1 = new Promise.Builder().promiseHandler(handler->2*3).build();
IPromise p2 = new Promise.Builder().promiseHandler(handler->{
    throw new RuntimeException("手動拋出異常");
}).build();
IPromise p = Promise.waitAll(p1,p2).then(resolvedData -> {
    Object[] datas = (Object[]) resolvedData;
    for(Object d:datas){
        if(d instanceof Throwable){
            ((Throwable)d).printStackTrace();
        }else{
            System.out.println(d);
        }
    }
    return datas;
});
複製代碼

輸出結果

6
java.lang.RuntimeException: 手動拋出異常
複製代碼

p1爲正常執行完畢,其終值爲6,p2手動拋出異常,使用waitAll後,p的終值爲一個數組,遍歷數組須要判斷值的類型。

相似於all,Promise.race方法將多個 Promise p1,...pn實例,包裝成一個新的 Promise 實例 p,只要p1-pn有一個狀態發生改變(不管是轉變爲正常狀態仍是異常狀態),p的狀態當即改變,並嘗試取消其他promise的執行。第一個改變的promise的狀態和終值做爲p的狀態和終值

Promise.resolve和Promise.pTry

這兩個方法都是Promise的靜態方法。Promise.resolve方法有多個重載,最重要的一個是resolve(Object object,String methodName,List<Object> args),該方法是將object的指定方法以異步方式執行,該方法的執行結果做爲Promise的終值,具體可參考wiki Promise.resolve

pTry方法將object的指定方法以同步方式執行,該方法的執行結果做爲Promise的終值,若是object爲IPromise實例,將忽略methodName和args參數,異步執行該實例。
該方法是以Promise統一處理同步和異步方法,無論object是同步操做仍是異步操做,均可以使用then指定下一步流程,用pCatch方法捕獲異常,避免開發中出現如下狀況

try{
  object.doSomething(args1,args2);//可能會拋出異常
  promise.then(resolvedData->{
      //一些邏輯
  }).then(resolvedData->{
      //一些邏輯
  }).pCatch(e->{
      //異常處理邏輯
  })
}catch(Exception e){
  //異常處理邏輯
}
複製代碼

使用pTry,能夠簡化異常處理

List args = new ArrayList(){args1,args2};
Promise.pTry(object,"doSomething",args)
.then(resolvedData->{
      //一些邏輯
}).then(resolvedData->{
  //一些邏輯
}).pCatch(e->{
  //異常處理邏輯
})
複製代碼
相關文章
相關標籤/搜索