回调地狱
1. 异步操作
var fs = require('fs'); fs.readFile('./views/index.html', (err, data) => { if (err) { throw err } console.log(data.toString()); }) fs.readFile('./views/main.html', (err, data) => { if (err) { throw err } console.log(data.toString()); }) fs.readFile('./views/update.html', (err, data) => { if (err) { throw err } console.log(data.toString()); })
在异步操作中,由于操作系统分片工作机制,下面三个文件的输出顺序是不确定的,后执行的可能会先输出。若要保证输出顺序,在前一个异步操作的回调函数中调用后一个异步操作。
var fs = require('fs'); fs.readFile('./views/index.html', (err, data) => { if (err) { throw err } fs.readFile('./views/main.html', (err, data) => { if (err) { throw err } fs.readFile('./views/update.html', (err, data) => { if (err) { throw err } console.log(data.toString()); }) console.log(data.toString()); }) console.log(data.toString()); })
这种情况下便出现了回调地狱。当异步操作越多,这种嵌套的层级也就越复杂,不利于代码维护。
2. Promise
- pending: 初始状态,既不是成功,也不是失败状态。
- fulfilled: 意味着操作成功完成。
- rejected: 意味着操作失败。
pending 状态的 Promise 对象可能会变为fulfilled 状态并传递一个值给相应的状态处理方法,也可能变为失败状态(rejected)并传递失败信息。当其中任一种情况出现时(settled),Promise 对象的 then
方法绑定的处理方法(handlers )就会被调用(then方法包含两个参数:onfulfilled 和 onrejected,它们都是 Function 类型。当Promise状态为fulfilled时,调用 then 的 onfulfilled 方法,当Promise状态为rejected时,调用 then 的 onrejected 方法),将相应的回调函数放入微任务(microtask)中
var p1 = new Promise((resolve, reject) => { console.log('before async'); fs.readFile('./views/index.html', (err, data) => { if (err) { reject(err); } console.log('in async') resolve(data); }) }) console.log('3333');
结果:
// before async // 3333 // in async
注意:Promise本身只是个容器,不是异步的,其中要完成的任务才是异步的。
p1.then((data) => { console.log(data); }, (err) => { console.log('出错了', err) })
then方法接受的第一个(函数)参数,对应着生成Promise函数中接收的resolve;第二个(函数)参数,对应着reject.
then方法的执行结果是一个promise。我们可以链接任意个then,前一个then的回调结果将作为参数传递给下一个then回调!
解决回调地狱的多重嵌套问题——promise链式编程
var p1 = new Promise((resolve, reject) => { console.log('before async'); fs.readFile('./views/index.html', (err, data) => { if (err) { reject(err); } console.log('in async') resolve(data); }) }) var p2 = new Promise((resolve, reject) => { console.log('before async'); fs.readFile('./views/update.html', (err, data) => { if (err) { reject(err); } console.log('in async') resolve(data); }) }) p1.then((data) => { console.log(data.toString()); // return 返回的结果会在后面紧接着的then方法中被函数接收 // return 123, data就是123;return p2,是一个resolved promise, data接收到的就是resolve包裹的值 return p2; }, (err) => { console.log('出错了', err) }).then((data) => { console.log(data); } )
封装
function pReadFile(path) { return new Promise((resolve, reject) => { fs.readFile(path, (err, data) => { if (err) { reject(err); } resolve(data); }) }) } var p1 = pReadFile('./views/index.html'); var p2 = pReadFile('./views/update.html'); p1.then((data) => { console.log(data.toString()); return p2; }, (err) => { console.log('出错了', err) }).then((data) => { console.log(data.toString()); } )
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通