Flutter心得 -- 从JavaScript的回调到Dart的Future

最近由于其它项目紧急,Flutter的摸索断断续续,并没有实质性的进展。今天抽空要将之前js的一些逻辑改到dart中,发现一个纠结的问题:

JavaScript的回调函数怎么优雅地转到Dart ?

正常来讲,js的回调,其实是可以转化为Promise, 那么Promise对应Dart的Future是显而易见的,有什么纠结的呢?

const callback=()=>{
    console.log('这里是回调');
}
var promise = new Promise((resolve, reject)=>{
    resolve();
});

promise.then(callback);
Function callback = () {
   print('这里是回调');
};

var future = Future.delayed(Duration(seconds: 2), (){
   print('future');
});

future.then((val){
   callback();
});

但是有些js回调是不方便直接转化为Promise的

var isLogining=false;
var isLogin=false;
var successQueue=[];

function login(callback){
    if(isLogin){
        callback();
        return;
    }
    successQueue.push(callback);
    if(isLogging){
        return;
    }
    isLogging = true;
    
    //async do login
    
    isLogin=true;
    isLogging =false;
    var func;
    while(func=successQueue.shift()){
        func();
    }
}

//调用
login(()=>{
    //处理登录后的事情
});

基本逻辑就是全局有一个自动登录操作,其它地方操作需要先经过登录操作验证,如果验证成功就回调

为防止多个调用同时进行时登录操作冲突,就需要记录一个是否在登录的状态,如果有同时的调用,就加入队列,登录操作成功后统一调用。

这个逻辑,要改Promise,是可以利用Promise参数的回调实现的

var isLogining=false;
var isLogin=false;
var successQueue=[];


function login(){
    if(isLogin){
        return new Promise((resolve, reject)=>{
            if(isLogin){
                resolve();
            }else{
                reject();
            }
        });
    }
    
    if(isLogging){
        return new Promise((resolve, reject)=>{
            successQueue.push(()=>{
                if(isLogin){
                    resolve();
                }else{
                    reject();
                }
            });
        });
    }
    isLogging = true;
    
    //async do login
    return Promise((resolve, reject)=>{
        
        isLogin=true;
        isLogging =false;
        var func;
        while(func=successQueue.shift()){
            func();
        }
        resolve();
    });
}

原理就是利用创建Promise时的回调参数,再次进行事件监听回调

但是,Dart的Future并没有回调参数,怎么实现呢?

搜索了下网友的相关文章,基本都是介绍Future, async, await的基本用法的。


思路1:在Future中循环延时判断


感觉方法有点蠢,具体逻辑未实现


思路2:ChangeNotifyer


这是一个专门做状态管理的通知类,但是还是没办法和Future结合,如果用回调函数的方式,是可以做事件通知并回调。

但这个回调没办法用Future实现


正解:Completer 


这是async包中的一个类,专门做事件通知的,完美结合了Future类

var complete = Completer();
    complete.future.then((val) {
      print('future success:' + val);
    });
    print('before delay');
    var future = Future.delayed(Duration(seconds: 2), () {
      complete.complete('aaa');
      print('after complete');
      complete.future.then((val) {
        print('future3 success:' + val);
      });
      complete = Completer();
      complete.future.then((val) {
        print('future reconstruct success:' + val);
      });
      complete.complete('bbb');
    });

    print('after delay');
    complete.future.then((val) {
      print('future2 success:' + val);
    });

可以看到,创建了Completer实例后,可以在实例的future属性上追加处理。

当调用了 complete方法后,就会依次处理相应的任务。

1. complete只能调用一次。

2. complete调用后,新追加的处理,依然可以执行,而且是马上执行

要重置事件,重新实例化就可以了。

这就完美契合了之前的需求,不需要用回调队列来伪装Future了

class LoginState{
    var isLogining=false;
    var isLogin=false;
    var loginComplete;
    
    Future<bool> login(){
        if(isLogin || isLogging){
            return loginComplete.future;
        }
        
        isLogging = true;
        loginComplete = Completer();
        
        //async do login
        return Future.delayed(Duration(seconds: 2), ()=>{
            
            isLogin=true;
            isLogging =false;
            loginComplete.complete(true);
            
            return true;
        });
    }
}


注:相关代码仅用于演示,可能不符合实际需求中的逻辑。