详谈Promise用法及实现原理

本文详细介绍了JavaScript中Promise的概念及其优雅地解决异步操作问题的方式。通过对比传统异步处理方法,阐述了Promise的基本语法、应用场景及其实现原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一,为什么要学Promise?

我们知道js中代码执行的顺序是从上到下同步执行,那么要想在同步函数中拿异步函数中的结果,我们会怎么做呢?

细想之下除了回调函数目前还没有其他的办法,而Promise的原理就是回调函数,你可能要问了,那干脆直接用回调函数不就行了,当然可以,不过不过优雅,而Promise就很优雅。

1.Promise的语法

// Promise 是es6新增的一个类, 可以解决异步操作上的弊端和问题
var fs = require("fs")
// 需求: 使用fs模块异步读取一个文件, 在异步回调函数外部获取并使用读取结果

// 方案1: 使用全局变量记录异步结果并在需要的地方打印
var result = null;
fs.readFile("./1, es6简介.txt", function(err,data){
    console.log(1, err, data)
    result = data.toString()
})
console.log(2, result) // 空数据

// 方案2: 使用函数返回值把异步结果返回,外部调用函数得到返回值
function method(){
    fs.readFile("./1, es6简介.txt", function(err,data){
        return data.toString();
    })
}
console.log(3, method()); // undefined
setTimeout(() => {
    console.log(4, method());  // undefind
}, 1000);

// 方案3: 使用回调函数的结构,把异步结果放入回调函数中, 在回调函数中获取异步结果
function method1(callback){
    fs.readFile("./1, es6简介.txt", function(err,data){
        callback(data)
    })
} 
method1(function(result){
    console.log(5, result)
})
// 总结: 以上三种方案, 1,2是传统方案, 拿不到异步结果, 第三种使用回调函数的写法可以拿到结果, 第三种方案就是一个最简单的promise结构 (只是promise的雏形)

// 以下是es6中promise基本语法
// 新建一个promise对象, 参数是回调函数, 回调函数有两个参数
var promise = new Promise(function(resolve, reject){
    // resolve 和 reject 也是两个函数, 
    // 在promise对象中开始一个异步操作
    fs.readFile("./1, es6简介.txt", function(err,data){
        // 如果异步任务出错,就执行reject函数,传入错误信息
        if(err)  reject(err.message);
        // 如果异步任务成功, 就执行resolve函数, 传入成功的数据
        else resolve(data)
    })
})
// 用promise对象调用then方法,传入成功的回调函数,对应resolve
promise.then(function(res){
    console.log(6, res)
})
// 用promise对象调用catch方法,传入失败的回调函数,对应reject
promise.catch(function(err){
    console.log(6, err)
})
// 上边是分别调取成功和失败的回调, 也可以使用下边的链式调用结构, 因为then的返回值依然是这个promise对象
promise.then(function(res){
    console.log(6, res)
}).catch(function(err){
    console.log(6, err)
})
// 其实then函数可以直接传入两个回调,一个成功函数一个失败函数
promise.then(function(res){
    console.log(6, res)
},function(err){ 
    console.log(6, err)
})

// 注意: 
// 1, 成功的回调函数resolve是必选的,必须在then中传入, 失败的回调函数reject是可选的, 可以省略
// 2, then函数获取promise异步结果不管在任何时间,任何位置调用, 不管调用多少次, 总能拿到异步结果

2.Promise的用法

// 需求: 读取data目录下的4个文件中的4句诗, 并按顺序拼接
var fs = require("fs")

// 方案一: 使用fs同步读取
var data1 = fs.readFileSync("./data/a.txt")
var data2 = fs.readFileSync("./data/b.txt")
var data3 = fs.readFileSync("./data/c.txt")
var data4 = fs.readFileSync("./data/d.txt")
console.log(1, data1 + data2 + data3 + data4)
// 缺点: 大量的同步操作会阻塞进程,造成代码响应迟缓,降低效率

// 方案二: 使用fs异步读取
fs.readFile("./data/a.txt",function(err,data1){
    fs.readFile("./data/b.txt",function(err,data2){
        fs.readFile("./data/c.txt",function(err,data3){
            fs.readFile("./data/d.txt",function(err,data4){
                console.log(2, data1+data2+data3+data4)
            })
        })
    })
})
// 缺点: 多个文件读取造成异步回调多层嵌套, 结构复杂, 可读性差

// 方案三: 使用promise解决异步任务多层嵌套问题
new Promise(function(resolve){
    fs.readFile("./data/a.txt",function(err,data){
        resolve(data)
    })
}).then(function(data1){
    return new Promise(function(resolve){
        fs.readFile("./data/b.txt",function(err,data2){
            resolve(data1+data2)
        })
    })
}).then(function(data12){
    return new Promise(function(resolve){
        fs.readFile("./data/c.txt",function(err,data3){
            resolve(data12+data3)
        })
    })
}).then(function(data123){
    return new Promise(function(resolve){
        fs.readFile("./data/d.txt",function(err,data4){
            resolve(data123+data4)
        })
    })
}).then(function(data){
    console.log(3, data)
})
// 缺点: promise虽然解决了多任务嵌套问题, 但是以上三种方案存在一个共性弊端, 就是多异步任务并发执行的问题, 如,a,b,c,d四个文件的读取, 上边的写法都是按顺序读取,上一个读完再读下一个, 相当于过独木桥, 每个时刻只有一个文件在读, 效率很低, 假设 四个文件读取分别耗时 5ms, 12ms, 8ms, 6ms  那么读取结束需要的总时间是 31ms , 如果可以让多个异步同时开始执行也就是并发执行, 就可以个大大提高执行效率

// 方案四: 使用promise解决多异步任务并发执行问题
var p1 = new Promise(function(resolve){
    fs.readFile("./data/a.txt",function(err,data){
        resolve(data)
    })
})
var p2 = new Promise(function(resolve){
    fs.readFile("./data/b.txt",function(err,data){
        resolve(data)
    })
})
var p3 = new Promise(function(resolve){
    fs.readFile("./data/c.txt",function(err,data){
        resolve(data)
    })
})
var p4 = new Promise(function(resolve){
    fs.readFile("./data/d.txt",function(err,data){
        resolve(data)
    })
})
// 注意: promise中的异步任务是在new创建是直接开始执行的, then函数只是读取异步结果, 而不是开始执行异步请求
// Promise 中提供了两个类方法:  all   race
// 把多个promise对象合并成一个, 参数是数组,数组中放多个promise示例, 返回一个新的promise对象
var mergeP = Promise.all([p1,p2,p3,p4])
// 使用合并后的对象调用then获取所有promise的结果, 是一个数组,数组中数据顺序和all参数顺序保持一致 (注意不是按照执行结束的先后顺序排列)
mergeP.then(function(dataArr){
    console.log(4, dataArr.join(""))
})
// all: 当合并的所有promise全部完成之后, 才会执行then回调,拿到所有结果
// race: 任意一个合并的promise任务完成, 立即调用then回调, 只能拿到这一个结果


// 类方法: 使用类名直接调用的方法   如: Promise.all()   Promise.race()
// 实例方法: 创建出来的对象调用的方法   如 :  p1.then(),  p1.catch()

// 总结: promise的两个主要用法
// 1, 解决多异步任务多层嵌套问题
// 2, 解决多异步任务并发问题

3.Promise的原理

// promise的基本结构语法
// var promise = new Promise(function(resolve, reject){
//     setTimeout(() => {
//         var random = Math.random()
//         if(random < 0.7) resolve("成功的数据")
//         else reject("错误的信息")
//     }, 1000);
// })
// promise.then(function(data){ 
//     console.log(data) 
// }, function(err){ 
//     console.log(err)
// })

// 下边, 我们自己写一个类MyPromise, 模拟实现它的构造函数和then方法
// 自定义类的构造函数实现
function MyPromise(callback){
    console.log(1, "执行了MyPromise的构造函数");
    // 每一个promise对象内部,都会有一个状态信息, 有三个可能值
    // pending 状态 表示等待状态, promise对象的默认状态, 
    // resolve 状态 表示成功状态, 当调用了resolve函数时,状态变成成功状态
    // reject 状态 表示失败状态, 当调用了reject函数时,状态变成失败状态
    // 注意: 状态值只能变化一次,一旦变更为成功或失败状态,则会一致保持这个状态 
    this.state = "pending";  // 初始化状态
    console.log("out", this)  // promise对象
    // myResolve和myReject是在外部调用的,所以函数中this指向并不是当前promise对象
    let self = this
    // 定义成功时的回调函数
    function myResolve(data){
        console.log(3, data, this, self) // Object [global] , promise
        self.state = "resolve" // 修改状态值为成功
        self.value = data; // 成功时value属性记录成功数据
        self.success(data) // 成功时,调用then函数中成功回调
    }
    // 定义失败时的回调函数
    function myReject(err){
        console.log(3, err, this, self)
        self.state = "reject" // 修改状态值为失败
        self.msg = err;  // 失败时用msg属性记录失败信息
        self.fail(err) // 失败时,调用then函数中失败回调
    }

    // MyPromise在创建对象时,其回调函数会直接执行, 所以在构造函数中直接调用, 并传入成功和失败状态对应的函数
    callback(myResolve, myReject);
}
// then方法是promise对象调用的方法,所以定义到构造函数原型中
MyPromise.prototype.then = function(success, fail=()=>{}){
    // 由于then函数可以在任意时刻调用, 所以调用then时,promise状态值不确定
    if(this.state == "pending"){
        // 说明此时异步任务还未结束, 还不能调用success或fail, 此时可以把success和fail这个回调函数传入this这个promise对象, 在异步任务结束后调用
        this.success = success;
        this.fail = fail;
    }
    if(this.state == "resolve"){
        // 如果当前状态是成功状态, 则调用then参数中的第一个成功回调
        success(this.value) // 参数传入成功数据
    }
    if(this.state == "reject"){
        // 如果当前状态是失败状态, 则调用then参数中的第二个失败回调
        fail(this.msg) // 参数传入失败信息
    }

    // then函数返回当前promise对象, 用于链式调用
    return this;
}


// 使用构造函数创建实例对象, 创建时,构造函数会执行
var p = new MyPromise(function(resolve, reject){
    console.log(2, '执行了myPeomise回调')
    setTimeout(() => {
        var random = Math.random()
        if(random < 0.7) resolve("成功的数据")
        else reject("错误的信息")
    }, 1000);
})
// 在异步任务结束前,调用then函数
p.then(function(data){
    console.log(4, data)
},function(err){
    console.log(4, err)
}).then(function(){})
// 在异步任务结束后, 调用then函数
setTimeout(() => {
    p.then(function(data){
        console.log(5, data)
    },function(err){
        console.log(5, err)
    })
}, 2000);

### XGBoost的工作原理 XGBoost是一种基于梯度提升框架的机器学习算法,它通过构建一系列弱学习器(通常是决策树),并将其组合成强学习器来完成复杂的预测任务。其核心思想在于最小化目标函数中的损失项和正则化项[^3]。 #### 加法模型与目标函数 XGBoost采用加法模型的形式,即每一轮迭代都会新增一棵树 \( f_t(x) \),并将这棵树的结果加入到当前模型中。具体来说,第 \( t \) 轮更新后的预测值可以表示为: \[ \hat{y}_i^{(t)} = \hat{y}_i^{(t-1)} + f_t(x_i) \] 其中,\( f_t(x) \) 是本轮新增的一棵回归树,而 \( \hat{y}_i^{(t-1)} \) 表示前 \( t-1 \) 轮累积得到的预测值[^4]。 为了找到最优的 \( f_t(x) \),XGBoost定义了一个带正则化的目标函数: \[ \text{Obj}(\theta) = \sum_{i=1}^n l(y_i, \hat{y}_i) + \Omega(f_k) \] 这里,\( l(y_i, \hat{y}_i) \) 是损失函数,用于衡量预测值与真实值之间的差距;\( \Omega(f_k) \) 则是对单棵树复杂度的惩罚项,形式如下: \[ \Omega(f) = \gamma T + \frac{1}{2}\lambda ||w||^2 \] 其中,\( T \) 代表叶子节点的数量,\( w \) 是各叶子节点上的权重向量,参数 \( \gamma \) 和 \( \lambda \) 控制着正则化的强度[^3]。 #### 近似分裂点查找 在实际操作过程中,由于直接求解精确的最佳分割点可能代价过高,因此XGBoost引入了一种近似的分裂策略。该方法首先统计候选特征的所有实例分布情况,并按照增益最大原则选取最佳划分位置[^4]。 另外值得注意的是,当面对大规模稀疏输入数据时,XGBoost能够自动识别缺失值并合理分配样本至左子叶或右子叶方向之一,从而进一步提升了效率与效果[^4]。 ### 实现步骤概述 以下是利用XGBoost进行建模的主要流程: 1. **准备阶段**: 对原始数据集执行必要的预处理操作,比如填补空缺字段、转换类别型属性等; 2. **初始化设置**: 明确指定待解决的任务类型(如回归或者二元分类)、评价指标以及超参数范围; 3. **训练过程**: 使用历史经验指导下的随机搜索技术调整内部结构直至收敛为止; 4. **验证分析**: 借助交叉检验手段评估最终成果的质量水平; 5. **部署上线**: 将经过充分测试确认无误之后的成品导出保存以便后续重复调用[^1]。 ```python import xgboost as xgb from sklearn.datasets import load_boston from sklearn.model_selection import train_test_split from sklearn.metrics import mean_squared_error # Load data and split into training/testing sets. data = load_boston() X_train, X_test, y_train, y_test = train_test_split(data.data, data.target) dtrain = xgb.DMatrix(X_train, label=y_train) dtest = xgb.DMatrix(X_test, label=y_test) params = { 'objective': 'reg:squarederror', 'max_depth': 6, 'eta': 0.1, } bst = xgb.train(params=params, dtrain=dtrain, num_boost_round=100) preds = bst.predict(dtest) rmse = np.sqrt(mean_squared_error(y_test, preds)) print("RMSE:", rmse) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值