You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Perl中Promises与AnyEvent结合时后续then回调未执行问题求助

问题:AnyEvent与Promises结合时,条件满足后后续then回调未执行

尝试编写一个基于AnyEvent和Promises的同步逻辑:设置5秒定时器将$inputs设为'next',my_p_await函数每隔3秒检查条件是否满足。运行后发现,当条件满足($inputs为'next')后,后续的then回调并未执行,看不到****>==> Time: 0: 的输出,但AnyEvent的15秒定时器正常运行。

原代码:

#!/usr/bin/perl
use strict;
use warnings;
use AnyEvent;
use AE;
use Time::Piece;
use Promises backend =>['AnyEvent'], 'deferred';
use feature q(say);
use Data::Dumper;

$| = 1;
my $inputs = 'starting';
my $w2; $w2 = AE::timer 5, 0, sub {
    say '***> AE::io timer : pass 5sec : ',
        Time::Piece->new(AE::now, "%s")->strftime("%Y-%m-%d %H:%M:%S");
    say '***> making : inputs = next';
    $inputs = 'next';
};
my $w3; $w3 = AE::timer 15, 15, sub {
    say 'AE::io timer : passed 15 sec : ',
        Time::Piece->new(AE::now, "%s")->strftime("%Y-%m-%d %H:%M:%S");
};
sub delay {
    my ($dl, $cb) = @_;
    my $t;
    $t = AE::timer $dl, 0, sub {
        say '===> delay : delayed ', $dl, ' sec';
        undef $t;
        $cb->() if ref $cb eq 'CODE';
    };
}
sub my_p_await{
    my ($sec, $cond, $cb) = @_;
    my $d = deferred;
    say '===> my_p_await: staring...with ',$sec, ' sec: ',
        Time::Piece->new(AE::now, "%s")->strftime("%Y-%m-%d %H:%M:%S");
    my $result = eval { $cond->() };
    if(!$result) {
        say '===> my_p_await: condition is false';
        delay($sec, sub {
            $cb->() if ref $cb eq 'CODE';
            my_p_await($sec, $cond, $cb);
        });
    }else{
        say '===> my_p_await: condition is true';
        $d->resolve;
        return;
    }
    return $d->promise;
}

deferred->resolve->promise->then( sub {
    my_p_await(3, sub { $inputs eq 'next'},sub {say '0: cb..'} )
})
->then( sub {
    say '';
    say '****>==> Time: 0: ',
        Time::Piece->new(AE::now, "%s")->strftime("%Y-%m-%d %H:%M:%S");
});

AE::cv->recv;

运行结果:

===> my_p_await: staring...with 3 sec: 2024-08-26 18:55:13
===> my_p_await: condition is false
===> delay : delayed 3 sec
0: cb..
===> my_p_await: staring...with 3 sec: 2024-08-26 18:55:16
===> my_p_await: condition is false
***> AE::io timer : pass 5sec : 2024-08-26 18:55:18
***> making : inputs = next
===> delay : delayed 3 sec
0: cb..
===> my_p_await: staring...with 3 sec: 2024-08-26 18:55:19
===> my_p_await: condition is true
AE::io timer : passed 15 sec : 2024-08-26 18:55:28

错误原因

核心问题在于my_p_await函数的递归逻辑错误:

  • 第一次调用my_p_await时创建了一个deferred对象$d,并返回它的promise给第一个then回调。
  • 当条件不满足时,递归调用my_p_await,此时会创建新的deferred对象,但最初的$d从未被resolve。
  • 第一个then等待的是最初的promise,这个promise永远不会完成,因此后续的then回调永远不会被触发。

修复方案

修改my_p_await的逻辑,让递归检查时复用最初的deferred对象,而不是每次递归都创建新的。可以通过内部嵌套函数来保持对同一个deferred的引用:

修正后的代码:

#!/usr/bin/perl
use strict;
use warnings;
use AnyEvent;
use AE;
use Time::Piece;
use Promises backend =>['AnyEvent'], 'deferred';
use feature q(say);
use Data::Dumper;

$| = 1;
my $inputs = 'starting';
my $w2; $w2 = AE::timer 5, 0, sub {
    say '***> AE::io timer : pass 5sec : ',
        Time::Piece->new(AE::now, "%s")->strftime("%Y-%m-%d %H:%M:%S");
    say '***> making : inputs = next';
    $inputs = 'next';
};
my $w3; $w3 = AE::timer 15, 15, sub {
    say 'AE::io timer : passed 15 sec : ',
        Time::Piece->new(AE::now, "%s")->strftime("%Y-%m-%d %H:%M:%S");
};
sub delay {
    my ($dl, $cb) = @_;
    my $t;
    $t = AE::timer $dl, 0, sub {
        say '===> delay : delayed ', $dl, ' sec';
        undef $t;
        $cb->() if ref $cb eq 'CODE';
    };
}
sub my_p_await{
    my ($sec, $cond, $cb) = @_;
    my $d = deferred;
    
    # 内部函数处理循环检查逻辑,复用同一个deferred
    my $check_condition = sub {
        say '===> my_p_await: staring...with ',$sec, ' sec: ',
            Time::Piece->new(AE::now, "%s")->strftime("%Y-%m-%d %H:%M:%S");
        my $result = eval { $cond->() };
        if(!$result) {
            say '===> my_p_await: condition is false';
            delay($sec, sub {
                $cb->() if ref $cb eq 'CODE';
                $check_condition->(); # 递归调用内部函数,复用同一个$d
            });
        }else{
            say '===> my_p_await: condition is true';
            $d->resolve; # 条件满足时resolve最初的deferred
        }
    };
    
    $check_condition->(); # 启动第一次检查
    return $d->promise;
}

deferred->resolve->promise->then( sub {
    return my_p_await(3, sub { $inputs eq 'next'},sub {say '0: cb..'} ) # 注意要return promise
})
->then( sub {
    say '';
    say '****>==> Time: 0: ',
        Time::Piece->new(AE::now, "%s")->strftime("%Y-%m-%d %H:%M:%S");
});

AE::cv->recv;

关键修复点

  1. 复用deferred对象:通过内部嵌套函数$check_condition保持对同一个deferred对象$d的引用,所有检查逻辑都围绕这个对象进行,条件满足时直接resolve它。
  2. 返回promise到then链:第一个then回调中必须return my_p_await的promise,这样then链才能正确等待它完成。

修复后运行结果

===> my_p_await: staring...with 3 sec: 2024-08-26 19:00:00
===> my_p_await: condition is false
===> delay : delayed 3 sec
0: cb..
===> my_p_await: staring...with 3 sec: 2024-08-26 19:00:03
===> my_p_await: condition is false
***> AE::io timer : pass 5sec : 2024-08-26 19:00:05
***> making : inputs = next
===> delay : delayed 3 sec
0: cb..
===> my_p_await: staring...with 3 sec: 2024-08-26 19:00:06
===> my_p_await: condition is true

****>==> Time: 0: 2024-08-26 19:00:06
AE::io timer : passed 15 sec : 2024-08-26 19:00:15

内容的提问来源于stack exchange,提问作者doosoonk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 01:57:02