为什么回调函数不是一种好的异步编程方式

为什么对于异步编程,回调不是一种合适的方式,原因在于以下两点:

回调表示异步的方式是非线性的,非顺序的,很难理解

请看以下代码

javascript 复制代码
// 实际实现的复杂回调代码
function registerUser(username, password, callback) {
    // 1. 先检查用户名是否存在
    checkUsernameExists(username, function(err, exists) {
        if (err) {
            return callback(new Error("检查用户名失败: " + err.message));
        }
        
        // 条件判断:如果用户名已存在
        if (exists) {
            return callback(new Error("用户名已存在"));
        }
        
        // 2. 创建新用户
        createUser(username, password, function(err, user) {
            if (err) {
                // 错误处理:创建用户失败
                return callback(new Error("创建用户失败: " + err.message));
            }
            
            // 3. 发送欢迎邮件
            sendWelcomeEmail(user.email, function(err, emailResult) {
                if (err) {
                    // 错误处理:邮件发送失败,需要回滚!
                    console.error("邮件发送失败,回滚用户创建");
                    
                    // 回滚:删除已创建的用户
                    rollbackCreateUser(user.id, function(rollbackErr) {
                        if (rollbackErr) {
                            // 更复杂的错误:回滚也失败了!
                            console.error("回滚失败:", rollbackErr);
                        }
                        
                        // 最终回调,包含原始错误
                        return callback(new Error("发送欢迎邮件失败: " + err.message));
                    });
                    return; // 注意:这里必须 return!
                }
                
                // 4. 记录注册日志(可选操作,但不是必需的)
                if (shouldLogRegistration()) {  // 条件判断
                    logRegistration(user.id, function(err, logResult) {
                        if (err) {
                            // 注意:这里日志失败不影响主流程
                            console.warn("记录日志失败,但继续流程:", err);
                        }
                        
                        // 最终成功回调
                        callback(null, { 
                            user: user, 
                            emailSent: true,
                            logged: !err
                        });
                    });
                } else {
                    // 最终成功回调(不走日志分支)
                    callback(null, { 
                        user: user, 
                        emailSent: true,
                        logged: false
                    });
                }
            });
        });
    });
}

这段代码需要在多个嵌套函数中来回跳跃,而且错误处理代码被分散在了多个层级中,导致这段代码难读之一在于嵌套,之一在于if-else导致代码逻辑进入不同的函数处理中打断了线性的阅读

回调函数的信任问题

回调函数是控制反转的,在使用第三方函数的时候,回调函数把自己函数的一部分执行控制交给第三方函数本身

javascript 复制代码
ajax("url...", function () {
    //逻辑处理
})

当第三方函数如这里的ajax,并没有调用你的回调函数,或者说它并不是你期望的那样是异步的,又或者它把你打断函数比期望中的多调用或少嗲用了几次,都会导致程序出错,但是基于信任问题,我们可以特定的代码去解决一些问题,

调用超时

javascript 复制代码
function timeoutify(fn, delay) {
    var intv = setTimeout(function () {
        intv = null;
        fn(new Error("Timeout !"))
    }, delay);
    
    return function () {
        if (intv) {
            //没有超时
            clearTimeout(intv);
            fn.apply(this,[null].concat([].slice.apply(argument, 0)));
        }
    }
}

function foo (err, data) {
    if (err) {
        console.error(err);
    } else {
        console.log(data);
    }
}

ajax("url...", timeoutify(foo, 500));

调用过早

当不确定使用的api是否会不会永远的异步执行的时候使用

javascript 复制代码
function asyncify (fn) {
    var origin_fn = fn,
        intv = setTimeout(function () {
            intv = null;
            if (fn) fn();
        }, 0);
    fn = null;
    return function () {
        if (intv) {
            //调用过早
            var args = [].slice.call(argument);
            var bindArgs = [this].concat(args);
            
            fn = origin_fn.bind.apply(origin_fn, bindArgs);
            
            
        } else {
            //已经是异步
            orgin_fn.apply(this, argument);
        }
    }
}

这段代码最终能保证你的代码是异步执行的不管在什么情景下,所以尽管可以通过代码去解决一部分的回调信任问题,但是写这种代码的难度要高于业务本身的水平,而且代码也变得更加臃肿和难维护了

总结

基于上述,我们可以知道,为什么异步编程使用回调不是一种好的办法,一部分是回调的表示方式是非线性的非顺序的,一部分是回调函数的控制反转导致的信任问题。

相关推荐
jump_jump17 小时前
Ripple:一个现代的响应式 UI 框架
前端·javascript·前端框架
夏天想17 小时前
element-plus的输入数字组件el-input-number 显示了 加减按钮(+ -) 和 小三角箭头(上下箭头),怎么去掉+,-或者箭头
前端·javascript·vue.js
清风徐来QCQ18 小时前
SpringMvC
前端·javascript·vue.js
Swift社区18 小时前
ArkTS Web 组件里,如何通过 javaScriptProxy 让 JS 同步调用原生方法
开发语言·前端·javascript
Hi_kenyon18 小时前
快速入门VUE与JS(二)--getter函数(取值器)与setter(存值器)
前端·javascript·vue.js
全栈前端老曹18 小时前
【前端路由】React Router 权限路由控制 - 登录验证、私有路由封装、高阶组件实现路由守卫
前端·javascript·react.js·前端框架·react-router·前端路由·权限路由
zhuà!19 小时前
uv-picker在页面初始化时,设置初始值无效
前端·javascript·uv
摸鱼的春哥19 小时前
实战:在 Docker (Windows) 中构建集成 yt-dlp 的“满血版” n8n 自动化工作流
前端·javascript·后端
_Rookie._19 小时前
关于迭代协议:可迭代协议和迭代器协议,生成器函数 生成器对象的理解
javascript·python