Node.js&Promise的新理解&记一次异步编程的错误尝试
前言
在继续学习Node.js的异步编程过程中,最开始接触的是回调函数,用回调函数来处理异步请求,但这里就涉及到一个问题,如果对数据有很多层的回调函数处理的话,那么就会使得整个代码的可阅读性大大降低,就像一个>
符号一样,例如
const fs = require(‘fs‘) const process = require(‘child_process‘) fs.readFile(‘bin.js‘, ‘utf-8‘, (err, data) => { var fileData = ‘File data is: ‘ + data process.exec(‘ls‘, (err, stdout, stderr) => { var cmdData = ‘CMD data is:‘ + stdout fs.writeFile(‘result.txt‘, fileData + ‘\n‘ + cmdData, (err) => { if(err) throw err console.log(‘Write success!‘) }) }) })
就像这样形成一个向右的箭头型,并且括号嵌套多层也让人难以区分,所以开始用Promise来处理这繁杂的多回调过程。
Promise
在官方文档中,Promise有这种说明,Promise对象代表某个未来才会知道结果的事件 (一般是一个异步操作),一个Promise就是一个代表了异步操作最终完成或者失败的对象。Promise本质上是一个绑定了回调 的对象,而不是像callback异步编程那样直接将回调传入函数内部。
Promise对外提供了统一的API,可供进一步处理。Promise的最终
状态有两种: fulfilled
和 rejected
, fulfilled
表示Promise处于完成状态,rejected
表示Promise处于被拒绝状态,这两种状态都是Promise的已决议
状态,相反如果Promise还未被 决议
,它就处于 未决议(peding)
状态。
如上是Promise的较为官方的解释,我通俗理解下来,一个Promise就是可以对应封装一次回调函数,fulfilled
是指成功的回调函数处理,rejected
是指的是失败的回调函数处理。
(失败指的是回调函数执行过程中预期的错误,针对异常错误还是应该应该抛出异常并捕获)
这里来举一个由回调函数向Promise用法的转变例子,程序维护这样一个功能,从bin.js
文件中读取出数据以后将读取的数据写入到write.txt
文件,这是回调函数的写法
const fs = require(‘fs‘) fs.readFile(‘bin.js‘, ‘utf-8‘, (err, data) => { fs.writeFile(‘write.txt‘, data, (err) => { if(err) throw err console.log(‘Write success!‘) }) })
然后修改为Promise版本
const fs = require(‘fs‘) function promiseReadFile(fileName) { return new Promise((resolve, reject) => { fs.readFile(fileName, ‘utf-8‘, (err, data) => { if(err) reject(err) resolve(data) }) }) } function promiseWriteFile(data) { return new Promise((resolve, reject) => { fs.writeFile(‘write.txt‘, data, (err) => { if(err) reject(err) resolve(‘Write success!‘) }) }) } var file = promiseReadFile(‘bin.js‘) file .then((data)=>{ return promiseWriteFile(data) }) .then((data) => { console.log(data) })
Promise支持用then
关键字来构造Promise链的使用,上一个then
同样返回一个Promise对象,这样后一个then
就可以据此继续构造Promise链,虽然上面的代码量相较于回调函数版本来说更多,但相较于回调函数版本Promise链上每一步的功能划分都更清晰,链上每一步都可以用单独的函数来切割开,只需要保证每次都返回封装的Promise对象即可。例如将读文件
和写文件
两部分别用两个Promise对象来接收,resolve
中接收的数据就是then
函数中的回调函数接收的数据data
。
一种错误异步编程的思维
在最开始理解Promise时,整体理解上很模糊,例如上述的Promise版本的代码,我最开始是这样书写的
const fs = require(‘fs‘) function promiseReadFile(fileName) { return new Promise((resolve, reject) => { fs.readFile(fileName, ‘utf-8‘, (err, data) => { if(err) reject(err) resolve(data) }) }) } file .then((data)=>{ fs.writeFile(‘write.txt‘, data, (err) => { if(err) console.log(err) return ‘Write success!‘ }) }) .then((data) => { console.log(data) })
上述代码我后来反思出有两个主要问题:
一个是很直接的错误,
then
如果要构造return
返回值,那么也应该返回一个Promise对象,下一个then
处理时,才能正确处理,而不会直接返回String
值给下一个then
方法另外一种虽然用法没错,但是是存在思维错误
.then((data)=>{ fs.writeFile(‘write.txt‘, data, (err) => { if(err) console.log(err) return ‘Write success!‘ }) })
then
函数中不建议进行逻辑操作,then
函数中应该尽可能简洁明了,如果then
函数体内包括太多东西的话,那就回到了使用回调函数的部分,没有达到用Promise链来分割简化嵌套回调函数的目的
Promise使用的建议
- 将每一步逻辑执行都单独封装在一个返回Promise对象的方法中
then
函数的回调函数中内容尽可能简单明了,保证Promise链的直观可读性
同时注意一下Promise存在的使用上的问题
- 不可取消,不可打断
- 一经决议就不可变
“不可取消,不可打断”是指在Promise链执行过程中,不能在中间某个状态下暂停(与yield相比),除非出现异常,导致整条Promise链执行中止
“一经决议就不可变”一旦从peding
状态转向fulfilled
和 rejected
状态,Promise的状态就永远确定了,不能从fulfilled
和 rejected
再返回peding
状态