JavaScript异步编程中用promise读取相互关联的json文件

这篇博客探讨了JavaScript中回调地狱的问题,特别是在处理链式异步操作时的复杂性和可读性降低。通过一个读取多个JSON文件的案例,展示了传统回调函数的千层饼式调用。然后,文章介绍了Promise作为异步编程的新解决方案,如何利用Promise的then方法和catch方法实现更清晰的异步代码结构,避免回调地狱,提高代码可读性和错误管理效率。
摘要由CSDN通过智能技术生成

JavaScript异步编程中,解决回调地狱的两种方式

众所周知,在ES6推出之前,要想执行一串有前后依赖关系的异步操作需要通过纯回调函数的方式,即把外层的函数所得到的结果作为参数传入到下一层函数, 试着看这样一个例子:

案例

根目录下有一个index.js脚本(也就是我们要编写读取文件的脚本),要读取 /files 文件夹里的三个json文件,分别为:

a.json

{
	"next" : "b.json",
	"msg": "this is a"
}

b.json

{
	"next" : "b.json",
	"msg": "this is a"
}

c.json

{
	"next" : "null",
	"msg": "this is a"
}

可以看出这三个文件之间有着链式的关系,a.json的next属性指向b.json,同样b.json的next属性指向c.json,要异步读取这三个文件,我们可以采用下面这种方式:

千层饼回调地狱

//方案一 回调地狱读取文件
function getFileContent(filename, successCallback, errorCallback) {
    const fullName = path.resolve(__dirname, 'files', filename)
    fs.readFile(fullName, (err, data) => {
        if (err) {
            //把异常抛出给errorCallback回调函数
            errorCallback(err)
            return
        }
        successCallback(JSON.parse(data.toString()))
    })
}
//每次调用getFileContent都要指定filename, successCallback, errorCallback
//而读取文件成功之后触发successCallback函数,新一层getFileContent就包含在其中
getFileContent('a.json', aData => {
    console.log(aData)
    getFileContent(aData.next, bData => {
        console.log(bData)
        getFileContent(bData.next, cData => {
            console.log(cData)
        }, err => {
            console.log(err)
        })
    }, err => {
        console.log(err)
    })
}, err => {
    console.log(err)
})

这种老千层饼式的调用大大降低了代码的可读性!!!而且在异常处理上,每一层都有自己的错误回调函数,不方便管理。

同时注意,下面这种避免回调地狱的方式是错误的,是对JavaScript 单线程异步机制的误解。下面这段代码,妄图在读取完aData之后将aData.next作为参数传递给下一个getFileContent函数,继续读取bData。但是,由于读取aData的fs.readFile是异步操作,需要等待js的主线程的callstack执行完才会把结果传回来,这个过程中就包括了执行读取a.nextgetFileContent。当主线程的callstackpop到getFileContent(global.a.next,bData=>...}时,傻眼了,因为此时读取aData的结果还在callback stack里候着呢,所以a是undefined,哪里有next属性…于是出发了TypeError

getFileContent('a.json', aData => {
    console.log(aData)
    global.a = aData
}, err => {
    console.log(err)
})

getFileContent(global.a.next,bData=>{ //TypeError,因为
    console.log(bData)
    global.b = bData
},err =>{
    console.log(err)
})

getFileContent(global.b.next,cData=>{
    console.log(cData)
    global.c = cData
},err =>{
    console.log(err)
})

Promise解决回调地狱

Promise 是异步编程的新解决方案(旧的就是刚才说的纯回调机制)。Promise对象有三个状态:pending(正在执行),resolved(执行成功),rejected(执行失败)。这三个状态之间只能有两个转移方式:

  1. pending 变为 resolved
  2. pending 变为 rejected

且一个promise对象只能改变一次。

无论成功和失败,promise都会有都会有一个结果数据,成功的数据一般为value;失败的结果一般为reason。

  • Promise的运行流程可以参考下图:
    在这里插入图片描述
    首先,Promise()函数本身的回调函数(resolve,reject)=>void是一个同步回调,也就是说,promise对象一产生就会执行它绑定的回调函数。而resolve和reject则所绑定的回调函数(通过.then()的方式绑定)因为异步的原因,在同步任务执行完成才会执行。具体可以参考这个例子:
let promise = new Promise(function(resolve, reject) {
  console.log('Promise');
  resolve();
});

promise.then(function() {
  console.log('resolved.');
});

console.log('Hi!');

// Promise
// Hi!
// resolved

具体到读取文件的任务,我们可以用ES6中的新构造函数:Promise 来创建一个promise对象,并用下面这个模板来解决上述的读取文件的问题:

//方案二 promise异步读取文件
//最外层是封装好的执行任务的函数getFileContent
function getFileContent(filename) {
	//第一件事就是新建一个Promise对象,并绑定回调函数
    const promise = new Promise((resolve, reject) => {
        const fullName = path.resolve(__dirname, 'files', filename)
        fs.readFile(fullName, (err, data) => {
            if (err) {
                reject(err)
                return
            }
            resolve(JSON.parse(data.toString()))
        })
    })
    return promise
}

getFileContent('a.json').then((aData) => {
    console.log(aData)
    return getFileContent(aData.next)
}).then((bData)=>{
    console.log(bData)
    return getFileContent(bData.next)
}).then((cData)=>{
    console.log(cData)
}).catch((err)=>{
    console.log(err)
})
  1. 可以看到,由于每次执行getFileContent()都会返回一个promise对象,所以可以直接在getFileContent('a.json')的后面用.then()来绑定回调函数。
  2. 为了链式调用,每次执行.then()回调函数都会调用下一个getFileContent()并把返回的promise对象暴露给下一个.then()
  3. 最后,用一个.catch()来统一管理链式调用中所产生的错误,无论是哪一个promise出错了,那么这个调用环就断了(因为这里每一环.then()都只写了第一个参数,即onresolved的回调,并没有指定第二个参数,即onrejected函数),所以异常传透给最后的.catch统一处理。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值