Node.js&Promise的新理解&记一次异步编程的错误尝试

88520191 2020-05-20

前言

在继续学习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的最终状态有两种: fulfilledrejectedfulfilled 表示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状态转向fulfilledrejected状态,Promise的状态就永远确定了,不能从fulfilledrejected再返回peding状态

相关推荐