Promise的简单理解

68 阅读4分钟

场景:异步函数的依次顺序执行

同时出现多个异步函数,执行顺序并不是按照代码的上下文顺序执行。同步任务是自上而下,但是异步任务并不是,在被放到eventLoop之后,执行顺序会因为各种因素导致不一样,比如说读取文件的大小。

  • 代码示例,在nodejs环境下进行文件读取,这是个异步操作,下面的代码是按照顺序自上而下书写的,但是结果并不是按照顺序完成,并且会发现每次输出结果都不一样。
const fs = require('fs')
const path = require('path')
fs.readFile(path.join(__dirname, './files/a.txt'), 'utf-8', (err, data) => {
    console.log('1----', data)})
fs.readFile(path.join(__dirname, './files/b.txt'), 'utf-8', (err, data) => {
    console.log('2----', data)})
fs.readFile(path.join(__dirname, './files/c.txt'), 'utf-8', (err, data) => {
    console.log('3----', data)})
fs.readFile(path.join(__dirname, './files/d.txt'), 'utf-8', (err, data) => {
    console.log('4----', data)})
  • 结果:

image.png

那我们在不使用promise的情况下,如何手动实现按照顺序执行

  • 逐个嵌套,只有上一个执行完毕/成功之后再执行下一个
// 依次读取4个文件

const fs = require('fs')
const path = require('path')

fs.readFile(path.join(__dirname, './files/a.txt'), 'utf-8', (err, data) => {
    console.log('1----', data)


    fs.readFile(path.join(__dirname, './files/b.txt'), 'utf-8', (err, data) => {
        console.log('2----', data)


        fs.readFile(path.join(__dirname, './files/c.txt'), 'utf-8', (err, data) => {
            console.log('3----', data)



            fs.readFile(path.join(__dirname, './files/d.txt'), 'utf-8', (err, data) => {
                console.log('4----', data)


            })
        })
    })
})
  • 结果:

image.png

  • 但是这样的话又出现了一个问题,小批量的我们可以这样写,但是如果大批量的处理,是不是要无限嵌套下去,会形成回调地狱,这显然是不合理的。所以有了promise

自用的promise理解函数

const fs = require('fs')
const path = require('path')

// 使用 new 来创建一个 Promise 对象
const p = new Promise(

    // 这是 Promise 的参数(固定的)
    // - resolve 参数:返回成功的数据
    // - reject 参数:返回失败的数据
    (resolve, reject) => {

        // 异步行为放在这里
        fs.readFile(path.join(__dirname, './files/a.txt'), 'utf-8', (err, data) => {
            // 如果出错,则使用 reject 返回错误信息
            if (err) {
                reject(err)
            }
            // 如果成功,则使用 resolve 返回成功的数据
            else {
                resolve(data)
            }
        })

    }

)

// 通过 Promise 对象,处理成功的情况
p.then((data) => {
    console.log(data)
})

p.catch((err) => {
    console.log("出错啦", err)
})

  • 总结
  1. 我们新建的一个promise对象,接收一个参数,这个参数是一个回调函数,回调函数里面接收两个方法:resolve,reject。我们在这个回调函数里执行异步操作,异步操作会有返回,如果成功的话,执行resolve方法并把成功的数据返回出去。如果失败的话,执行reject方法,把失败的错误信息返回出去。
  2. 在promise里面返回的数据,通过resolve(成功)返回的数据,我们使用对象.then((data)=>{})的方式接收并进行下一步操作。通过reject(失败)返回的错误信息,我们使用对象.catch((err)=>{})的方式接收。
  • 特性
  1. promise对象有三个状态:
    • Pending 初始状态,进行中,既不是成功也不是失败。
    • Resolved 成功状态或者完成状态,也叫Fulfilled
    • Rejected 失败状态或者未完成状态
  2. 这三种状态变化,成功的话,pending变成resolved,失败的话,pending变成rejected。状态一旦改变就无法再次改变状态,这是promise(承诺)的由来,一个promise对象只能被修改一次。
  3. 成功了进入.then中,状态改为resolved,失败进入.catch中,状态改为rejected。

image.png

image.png

再次实现读取文件

// 依次读取4个文件

const fs = require('fs')
const path = require('path')

function readMyFile(fileName) {
    // 创建 Promise 异步处理对象
    const p = new Promise(
        (resolve, reject) => {
            fs.readFile(path.join(__dirname, './files', fileName), 'utf-8', (err, data) => {
                if (err) {
                    reject(err)
                } else {
                    resolve(data)
                }
            })
        }
    )
    // 返回 Promise 对象
    return p
}

// a.txt 1
// b.txt 3
// c.txt 2
// d.txt 5

readMyFile('a.txt')
    .then(data => {
        console.log('1----', data)

        // 返回一个 Promise,这个 Promise 可以被下一个 .then 处理
        return readMyFile('b.txt')
    })
    .then(data => {
        console.log('2----', data)

        return readMyFile('c.txt')
    })
    .then(data => {
        console.log('3----', data)

        return readMyFile('d.txt')
    })
    .then(data => {
        console.log('4----', data)
    })
  • 结果:

image.png

  • 注意
    • 在.then 里return 出来的又是一个promise对象,可以继续.then()调用,可以被下一个.then()处理。
    • 当返回一个非promise对象数据时,则下一个then的回调函数参数得到的就是这个值
    • 当返回一个promise对象时,则下一个then的回调函数参数得到的就是当前这个promise resolve出来的值
readMyFile('a.txt').then(data => {
    console.log('>>>>', data)

    // 当返回一个非 Promise 对象数据时,则下一个 then 的回调函数参数得到就是该值
    return 123

    // 当返回一个 Promise 对象时,则下一个 then 的回调函数参数得到的就是当前这个 Promise resolve 出来的值
    // return readMyFile('b.txt')
}).then(data => {
    console.log('>>>2>>>>', data)
})

image.png

  • 各自独立读取也可以实现
// 各自为政,自己执行完自己打印
readMyFile('a.txt').then(data => {
    console.log(data)
})

readMyFile('b.txt').then(data => {
    console.log(data)
})

readMyFile('c.txt').then(data => {
    console.log(data)
})

readMyFile('d.txt').then(data => {
    console.log(data)
})

image.png

promise的两个方法

  • Promise.all 同时读取,等待所有文件全部读取完成,再执行成功结果。同时进行多个promise异步操作,所有的promise都完成之后才执行。
// 同时读取,等待所有文件都读完,一次性打印
const p = Promise.all([
    readMyFile('a.txt'),
    readMyFile('b.txt'),
    readMyFile('c.txt'),
    readMyFile('d.txt'),
])

p.then(results => {
    console.log(results)
})

image.png

  • Promise.race 同时读取,同时执行多个异步函数,哪个异步成功了就直接执行.then,其他的就不管了。跟单词的意思一样,赛跑机制。
const p = Promise.race([
    readMyFile('a.txt'),
    readMyFile('b.txt'),
    readMyFile('c.txt'),
    readMyFile('d.txt'),
])

p.then(data => {
    console.log(data)
})
  • 不难看出,每次执行同一段代码,结果是不一样的,每次速度都不一样

image.png