Promise-在Java中以同步的方式非同步程式設計

忘語本尊發表於2018-06-02

java Promise

java promise(GitHub)是Promise A+規範的java實現版本。Promise A+是commonJs規範提出的一種非同步程式設計解決方案,比傳統的解決方案—回撥函式和事件—更合理和更強大。promise實現了Promise A+規範,包裝了java中對多執行緒的操作,提供統一的介面,使得控制非同步操作更加容易。實現過程中參考文件如下:

基本使用:

<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>
複製程式碼
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表示式,更加簡潔。

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)//捕獲中間可能產生的異常
複製程式碼

具體使用 方式參考promise-java非同步程式設計解決方案

Docs

promise規範

promise規範可以參考 Promise A+規範。其中ES6 Promise物件 在Promise A+規範上做了一些補充。java promise在使用上基本與ES6 Promise物件保持一致,部分地方有些許不同,後面會做出說明。 Promise的三個狀態

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

Promise

Promise是IPromise的實現,Promise例項一經建立,將立即非同步執行,部分介面如下

IPromise then(OnFulfilledExecutor onFulfilledExecutor)
  • 如果當前promise處於pending狀態,阻塞當前執行緒,等待promise狀態轉變為fulfilled或rejected
  • 如果處於fulfilled狀態,執行onFulfilledExecutor.onFulfilled(resolvedData)回撥。
    • 如果回撥返回一個Promise物件a,以a作為then方法的返回值,如果回撥返回一個普通物件obj,以obj作為終值、狀態為fulfilled包裝一個新Promise作為then方法的返回值
    • 如果執行回撥過程中產生異常e,返回一個以e作為拒因、狀態為rejected的新Promise,並拒絕執行接下來的所有Promise直到遇到pCatch。
  • 如果處於rejected狀態,執行onRejectedExecutor.onRejected(rejectReason)回撥,返回一個以當前promise的異常作為拒因、狀態為rejected的新Promise,並拒絕執行接下來的所有Promise直到遇到pCatch或pFinally
    引數:

IPromise pCatch(OnCatchedExecutor onCatchedExecutor);

then(null,onRejectedExecutor)的別名,但返回不同於then,出現異常時可以選擇不拒絕接下來Promise的執行,可用於異常修正,類似於try{}catch{}
該方法會嘗試捕獲當前promise的異常,最終返回一個新Promise,當被捕獲Promise處於不同的狀態時有不同的行為

  • pending:阻塞當前執行緒,等待pending轉變為fulfilled或rejected,行為同then
  • fulfilled:不執行回撥,以當前Promise終值和狀態返回一個全新的Promise
  • rejected:執行onCatched(Throwable catchReason)回撥。
    • 如果onCatched方法返回一個Promise,以這個Promise作為最終返回。
    • 如果onCatched方法返回一個非Promise物件obj,以obj作為終值、fulfilled狀態返回一個全新的物件。
    • 如果執行回撥過程中產生異常e,以e為拒因、狀態為rejected返回一個新的Promise,並拒絕執行接下來的所有Promise直到再次遇到pCatch
void listen(OnCompleteListener onCompleteListener);

指定一個監聽器,在promise狀態轉為fulfilled或rejected呼叫,該方法不會阻塞執行緒執行,可以多次呼叫指定多個監聽器

void pFinally(OnCompleteListener onCompleteListener);

listen的別名,行為同listen

Status getStatus()

獲取promise的當前狀態

Object getResolvedData()

獲取promise fulfilled狀態下的終值,其餘狀態下時為null

Throwable getRejectedData()

獲取promise rejected狀態下的拒因,其餘狀態下為null

Future getFuture()

獲取promise對應非同步任務的future

boolean cancel()

嘗試取消promise對應的非同步任務,底層呼叫future.cancel(true)。fulfilled或rejected狀態下無效。

Promise.Builder

Promise物件生成器

Builder pool(ExecutorService threadPool)

指定一個執行緒池用於執行promise任務,如果不指定,每一個promise都將啟動一個執行緒

Builder promiseHanler(PromiseHandler promiseExecutor)

指定promise執行器,在promiseHanler的run方法中實現執行緒的具體業務邏輯,注意==promise物件一經建立,將立即執行其中的邏輯==

Builder externalInput(Object externalInput)

向Promise注入一個外部引數,可以在指定PromiseHandler時通過PromiseExecutor.getExternalInput()獲取

int i = 3;
IPromise p = new Promise.Builder()
.externalInput(i).promiseHanler(new PromiseHandler() {
    public Object run(PromiseExecutor executor) {
        Integer args = (Integer) executor.getExternalInput();
        return args*2;
    }
}).build();
複製程式碼
Builder promise(IPromise promise)

指定一個promise x,使當前promise接受 x 的狀態

  • 如果 x 處於pending, 當前promise 需保持為pending直至 x 轉為fulfilled或rejected
  • 如果 x 處於fulfilled,用x的終值值執行當前promise,可以在指定PromiseHandler時通過PromiseExecutor.getPromiseInput()獲取
  • 如果 x 處於拒絕態,用相同的據因拒絕當前promise執行
ExecutorService fixedPool = Promise.pool(1);
IPromise promise1 = new Promise.Builder().pool(fixedPool).promiseHanler(executor->3).build();
IPromise promise2 = new Promise.Builder().pool(fixedPool)
    .promise(promise1)
    .promiseHanler(executor->4+(Integer) executor.getPromiseInput())
.build()
.then(resolvedData->{
    System.out.println(resolvedData);
    return resolvedData;
}, rejectedReason-> rejectedReason.printStackTrace());
複製程式碼

最終結果返回7,。如果promise1在執行過程中丟擲異常e,promise2將被拒絕執行,將會以e作為拒因,狀態為rejected返回一個新的Promise,最終會執行rejectedReason-> rejectedReason.printStackTrace()回撥。

IPromise build()

建立一個Promise例項

Promise的靜態方法

static IPromise all(IPromise ...promises)

將多個 Promise 例項p1,...pn,包裝成一個新的 Promise 例項 p,只有當p1-pn的狀態都轉為fulfilled時,p的狀態才為fulfilled,此時p1-pn的返回值包裝為一個陣列Object[r1,...rn]作為p的終值。
只要p1-pn中任意一個被rejected,p的狀態就轉為rejected,將第一個被rejected的promise的拒因作為p的拒因,並嘗試取消其餘promise的執行(內部呼叫future.cancel(true))

static IPromise race(IPromise ...promises)

將多個 Promise p1,...pn例項,包裝成一個新的 Promise 例項 p,只要p1-pn有一個狀態發生改變,p的狀態立即改變。並嘗試取消其餘promise的執行(內部呼叫future.cancel(true))
第一個改變的promise的狀態和資料作為p的狀態和資料

static IPromise resolve()

建立一個終值為null、fulfilled狀態的promise

static IPromise resolve(Object object)

建立一個終值為object、fulfilled狀態的promise

static IPromise resolve(Object object,List args)

將object的then方法以非同步方式執行,then方法的執行結果作為Promise的終值

static IPromise resolve(Object object,String methodName,List args)

將object的指定方法以非同步方式執行,該方法的執行結果作為Promise的終值,目標方法的引數必須按順序包含在List中,如object.doSomething(int a,Map b),用resolve執行為

List args = new ArrayList()
args.add(1);
args.add(map)
Promise.resolve(object,"doSomething",args);
複製程式碼
static IPromise reject(Object reason)

建立一個拒因為reason、rejected狀態的promise

static IPromise pTry(Object object,String methodName,List args)

將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->{
  //異常處理邏輯
})
複製程式碼

PromiseHandler

定義非同步邏輯的介面

Object run(PromiseExecutor executor)throws Exception;

run方法中實現具體的業務邏輯,最終run方式是線上程的call方法執行,如果run方法中含有wait、sleep...等鎖操作,可能需要自行處理InterruptedException。因為該執行緒可能被外部呼叫cancel()或interrupt()方法

PromiseExecutor

promise狀態處理

void resolve(final Object args)

將Promise物件的狀態從“未完成”變為“成功”(即從pending變為fulfilled)。注意該方法一經呼叫,promise狀態將不可改變,如下例,在呼叫executor.resolve(3);後,return之前丟擲一個異常,promise的狀態依舊是fulfilled,終值為3。

new Promise.Builder().promiseHanler(new PromiseHandler(){
    @Override
    public Object run(PromiseExecutor executor) {
        executor.resolve(3);
        throw new RuntimeException("error");
        return null;
    }
}).build()
複製程式碼

在run方法中executor.resolve(3)等同於return 3

@Override
public Object run(PromiseExecutor executor) {
    return 3;
}
複製程式碼

大多數情況下建議直接使用return返回promise的終值。

void reject(final Throwable args)

將Promise物件的狀態從“未完成”變為“失敗”(即從pending變為fulfilled)

Object getExternalInput()

獲取通過new Promise.Builder().externalInput(Object externalInput)方法注入的引數,具體參考Promise.Builder#externalInput(Object externalInput)

Object getPromiseInput()

獲內部promise的執行結果。通過new Promise.Builder().promise(promise1)指定的promise1的執行結果。具體參考 Promise.Builder#promise(IPromise promise)

OnFulfilledExecutor

fulfilled回撥介面

Object onFulfilled(Object resolvedData)throws Exception;

狀態轉為fulfilled時的回撥,返回值可以是IPromise例項或普通物件。如果object是IPromise例項,object作為then方法的返回值,如果object是個普通物件,以object作為終值、狀態為fulfilled包裝一個新Promise作為then方法的返回值

OnRejectedExecutor

rejected回撥介面

void onRejected(Throwable rejectReason)throws Exception;

當Promise轉變為rejected狀態時的回撥

OnCatchedExecutor

rejected回撥介面

Object onCatched(Throwable catchReason)throws Exception;

當發生異常時的回撥,最終返回一個Promise或普通物件,如果是一個普通物件,這個物件將作為下一個Promise的終值

OnCompleteListener

void listen(Object resolvedData,Throwable e);

當Promise執行結束時的回撥(無論是fulfilled還是rejected)

  • resolvedData fulfilled狀態時的終值,rejected狀態時為null
  • e rejected狀態時的異常資訊,fulfilled狀態時為null

示例

示例1:基本使用
new Promise.Builder().promiseHanler(new PromiseHandler(){
    @Override
    public Object run(PromiseExecutor executor) {
        executor.resolve(3);//返回非同步執行結果3
        return null;
    }
}).build().then(new OnFulfilledExecutor() {
    @Override
    public Object onFulfilled(Object resolvedData) {
        Integer i = ((Integer)resolvedData)+1;//獲取上一個promsie執行結果3,執行+1
        System.out.println(i);//輸出執行結果4
        //建立一個新的promise,將4作為該promise的輸入
        IPromise p = new Promise.Builder().externalInput(i).promiseHanler(new PromiseHandler() {
            @Override
            public Object run(PromiseExecutor executor) {
                //獲取外部輸入4
                Integer args = (Integer) executor.getExternalInput();
                executor.resolve(args*2);//執行 4x2
                return null;
            }
        }).build();
        return p;//返回該promise p
    }
})
.then(new OnFulfilledExecutor() {//執行p的回撥
    @Override
    public Object onFulfilled(Object args) {
        System.out.println(args);//輸出p的執行結果
        return args;
    }
}, new OnRejectedExecutor() {//捕獲可能出現的異常
    @Override
    public void onRejected(Throwable rejectedReason) throws Exception {
        rejectedReason.printStackTrace();
    }
});
複製程式碼

結果

4
8
複製程式碼
示例2
ExecutorService fixedPool = Promise.pool(1);//建立一個執行緒池
//建立promise1
IPromise promise1 = new Promise.Builder().pool(fixedPool).promiseHanler(executor->3).build();
//建立promise2
IPromise promise2 = new Promise.Builder().pool(fixedPool)
    .promise(promise1)//讓promise2接受promise1的狀態,優先執行promise1
    .promiseHanler(executor->{
        //獲取promise1的執行結果,執行promise2的邏輯
        return 4+(Integer) executor.getPromiseInput();
    })
    .build()
    .then(resolvedData->{
        System.out.println(resolvedData);//列印promise2的執行結果 
        return resolvedData;
    }, rejectedReason-> rejectedReason.printStackTrace());
System.out.println("end");
fixedPool.shutdown();
複製程式碼

結果

7
end
複製程式碼
示例3:錯誤處理
new Promise.Builder().promiseHanler(executor -> 3).build().then(resolvedData->{
    System.out.println("a:"+resolvedData);
    return new Promise.Builder().promiseHanler(executor -> {
        executor.reject(new RuntimeException("err"));//丟擲異常
        return null;
    }).build();
}).then(resolvedData1 -> {//fulfilled回撥
    System.out.println("b:"+resolvedData1);
    return resolvedData1;
},rejectReason -> {//rejected回撥
    System.err.println("c:"+rejectReason);
});
複製程式碼

結果

a:3
c:java.lang.RuntimeException: err
複製程式碼
示例4:pCatch
new Promise.Builder().promiseHanler(executor -> 0).build()
  .then(res0->{
    System.out.println("a:"+res0);//輸出 a:0
    Thread.sleep(100);
    return 1;//返回1
}).then(res1 -> {
    throw new RuntimeException("throw error");//丟擲異常
}).then(res2->{
    Thread.sleep(100);
    System.out.println("b:"+res2);
    return 2;
}).pCatch(e->{
    Thread.sleep(100);
    System.out.println("c:");//輸出c:
    e.printStackTrace();
    return 3;
}).then(res3->{
    Thread.sleep(100);
    System.out.println("d:"+res3);//輸出d:3
    return 4;
});
複製程式碼

結果

a:0
c:
runtimeException:throw error
d:3
複製程式碼

從上面結果可以看出,在res1出丟擲異常後,拒絕了res2處的執行,被pCatch捕獲,pCatch返回3,被包裝成終值為3、fulfilled狀態的promise,在res3列印d:3。

示例5:Promise.all(IPromise ...promises)
IPromise p1 = new Promise.Builder().promiseHanler(executor -> {
    Thread.sleep(1000);
    return 1;
}).build();
IPromise p2 = new Promise.Builder().promiseHanler(executor -> {
    Thread.sleep(4000);
    return 2;
}).build();
IPromise p3 = new Promise.Builder().promiseHanler(executor -> {
    Thread.sleep(2000);
    return 3;
}).build();
long s = System.currentTimeMillis();
Promise.all(p1,p2,p3).then(resolvedData -> {
    Object[] datas = (Object[])resolvedData;
    for(Object d:datas){
        System.out.println(d);
    }
    return null;
},e->e.printStackTrace());
System.out.println("耗時:"+(System.currentTimeMillis()-s));
複製程式碼

結果

1
2
3
耗時:4033
複製程式碼
示例6:執行緒取消
Map<String,Boolean> p1Flag = new HashMap<>();
p1Flag.put("flag",true);
IPromise p1 = new Promise.Builder().externalInput(p1Flag).promiseHanler(executor -> {
    while (((Map<String,Boolean>)executor.getExternalInput()).get("flag")){
        //do something
        System.out.println("p1 正在執行任務");
    }
    System.out.println("p1任務完成,正常結束");
    return 1;
}).build();
IPromise p2 = new Promise.Builder().promiseHanler(executor -> {
    while (!Thread.currentThread().isInterrupted()){
        System.out.println("執行p2正常邏輯");
    }
    System.err.println("p2執行緒被取消");
    return 2;
}).build();
IPromise p3 = new Promise.Builder().promiseHanler(executor -> {
    Thread.sleep(10);
    throw new RuntimeException("p3丟擲異常");
}).build();
IPromise p4 = new Promise.Builder().finalPromise("4",true).build();
long s = System.currentTimeMillis();
Promise.all(p1,p2,p3,p4).then(resolvedData -> {
    Object[] datas = (Object[])resolvedData;
    for(Object d:datas){
        System.out.println(d);
    }
    return null;
},e->e.printStackTrace());
System.out.println("耗時:"+(System.currentTimeMillis()-s));
p1Flag.put("flag",false);
複製程式碼

可能的結果如下

p1 正在執行任務
p1 正在執行任務
執行p2正常邏輯
執行p2正常邏輯
p1 正在執行任務 
runtimeException:p3丟擲異常
p2執行緒被取消
p1 正在執行任務
p1 正在執行任務
p1 正在執行任務 
p1任務完成,正常結束
複製程式碼

從上面結果可以看出,開始p1和p2都在正常執行,當p3丟擲異常後,Promise.all方法立即返回p3的異常並列印,同時取消p1和p2的執行,由於p2判斷了執行緒狀態Thread.currentThread().isInterrupted(),所以p2執行了正常的退出邏輯。p1仍然在執行,並沒有被取消掉,最後列印p1任務完成,正常結束是因為程式末尾執行了p1Flag.put("flag",false);,否則p1會永遠迴圈列印。

示例7:同步方法非同步執行
public class ThenTest {
    public Integer then(int a,int b){
        //列印當前執行現場名稱
        System.out.println(Thread.currentThread().getName());
        return a+b;
    }
    public static void main(String[] args){
        //列印主執行緒名稱
        System.out.println(Thread.currentThread().getName());
        List arg = new ArrayList<>();
        arg.add(1);
        arg.add(2);
        //將ThenTest例項then方法非同步執行
        Promise.resolve(new ThenTest(),arg).then(resolvedData -> {
            System.out.println(resolvedData);
            return resolvedData;
        }).pCatch(e->{
            e.printStackTrace();
            return 1;
        });
    }
}
複製程式碼

結果

main
promise-thread-0
3
複製程式碼

相關文章