详谈Promise用法及实现原理

一,为什么要学Promise?

我们知道js中代码执行的顺序是从上到下同步执行,那么要想在同步函数中拿异步函数中的结果,我们会怎么做呢?

细想之下除了回调函数目前还没有其他的办法,而Promise的原理就是回调函数,你可能要问了,那干脆直接用回调函数不就行了,当然可以,不过不过优雅,而Promise就很优雅。

1.Promise的语法

// Promise 是es6新增的一个类, 可以解决异步操作上的弊端和问题
var fs = require("fs")
// 需求: 使用fs模块异步读取一个文件, 在异步回调函数外部获取并使用读取结果

// 方案1: 使用全局变量记录异步结果并在需要的地方打印
var result = null;
fs.readFile("./1, es6简介.txt", function(err,data){
    console.log(1, err, data)
    result = data.toString()
})
console.log(2, result) // 空数据

// 方案2: 使用函数返回值把异步结果返回,外部调用函数得到返回值
function method(){
    fs.readFile("./1, es6简介.txt", function(err,data){
        return data.toString();
    })
}
console.log(3, method()); // undefined
setTimeout(() => {
    console.log(4, method());  // undefind
}, 1000);

// 方案3: 使用回调函数的结构,把异步结果放入回调函数中, 在回调函数中获取异步结果
function method1(callback){
    fs.readFile("./1, es6简介.txt", function(err,data){
        callback(data)
    })
} 
method1(function(result){
    console.log(5, result)
})
// 总结: 以上三种方案, 1,2是传统方案, 拿不到异步结果, 第三种使用回调函数的写法可以拿到结果, 第三种方案就是一个最简单的promise结构 (只是promise的雏形)

// 以下是es6中promise基本语法
// 新建一个promise对象, 参数是回调函数, 回调函数有两个参数
var promise = new Promise(function(resolve, reject){
    // resolve 和 reject 也是两个函数, 
    // 在promise对象中开始一个异步操作
    fs.readFile("./1, es6简介.txt", function(err,data){
        // 如果异步任务出错,就执行reject函数,传入错误信息
        if(err)  reject(err.message);
        // 如果异步任务成功, 就执行resolve函数, 传入成功的数据
        else resolve(data)
    })
})
// 用promise对象调用then方法,传入成功的回调函数,对应resolve
promise.then(function(res){
    console.log(6, res)
})
// 用promise对象调用catch方法,传入失败的回调函数,对应reject
promise.catch(function(err){
    console.log(6, err)
})
// 上边是分别调取成功和失败的回调, 也可以使用下边的链式调用结构, 因为then的返回值依然是这个promise对象
promise.then(function(res){
    console.log(6, res)
}).catch(function(err){
    console.log(6, err)
})
// 其实then函数可以直接传入两个回调,一个成功函数一个失败函数
promise.then(function(res){
    console.log(6, res)
},function(err){ 
    console.log(6, err)
})

// 注意: 
// 1, 成功的回调函数resolve是必选的,必须在then中传入, 失败的回调函数reject是可选的, 可以省略
// 2, then函数获取promise异步结果不管在任何时间,任何位置调用, 不管调用多少次, 总能拿到异步结果

2.Promise的用法

// 需求: 读取data目录下的4个文件中的4句诗, 并按顺序拼接
var fs = require("fs")

// 方案一: 使用fs同步读取
var data1 = fs.readFileSync("./data/a.txt")
var data2 = fs.readFileSync("./data/b.txt")
var data3 = fs.readFileSync("./data/c.txt")
var data4 = fs.readFileSync("./data/d.txt")
console.log(1, data1 + data2 + data3 + data4)
// 缺点: 大量的同步操作会阻塞进程,造成代码响应迟缓,降低效率

// 方案二: 使用fs异步读取
fs.readFile("./data/a.txt",function(err,data1){
    fs.readFile("./data/b.txt",function(err,data2){
        fs.readFile("./data/c.txt",function(err,data3){
            fs.readFile("./data/d.txt",function(err,data4){
                console.log(2, data1+data2+data3+data4)
            })
        })
    })
})
// 缺点: 多个文件读取造成异步回调多层嵌套, 结构复杂, 可读性差

// 方案三: 使用promise解决异步任务多层嵌套问题
new Promise(function(resolve){
    fs.readFile("./data/a.txt",function(err,data){
        resolve(data)
    })
}).then(function(data1){
    return new Promise(function(resolve){
        fs.readFile("./data/b.txt",function(err,data2){
            resolve(data1+data2)
        })
    })
}).then(function(data12){
    return new Promise(function(resolve){
        fs.readFile("./data/c.txt",function(err,data3){
            resolve(data12+data3)
        })
    })
}).then(function(data123){
    return new Promise(function(resolve){
        fs.readFile("./data/d.txt",function(err,data4){
            resolve(data123+data4)
        })
    })
}).then(function(data){
    console.log(3, data)
})
// 缺点: promise虽然解决了多任务嵌套问题, 但是以上三种方案存在一个共性弊端, 就是多异步任务并发执行的问题, 如,a,b,c,d四个文件的读取, 上边的写法都是按顺序读取,上一个读完再读下一个, 相当于过独木桥, 每个时刻只有一个文件在读, 效率很低, 假设 四个文件读取分别耗时 5ms, 12ms, 8ms, 6ms  那么读取结束需要的总时间是 31ms , 如果可以让多个异步同时开始执行也就是并发执行, 就可以个大大提高执行效率

// 方案四: 使用promise解决多异步任务并发执行问题
var p1 = new Promise(function(resolve){
    fs.readFile("./data/a.txt",function(err,data){
        resolve(data)
    })
})
var p2 = new Promise(function(resolve){
    fs.readFile("./data/b.txt",function(err,data){
        resolve(data)
    })
})
var p3 = new Promise(function(resolve){
    fs.readFile("./data/c.txt",function(err,data){
        resolve(data)
    })
})
var p4 = new Promise(function(resolve){
    fs.readFile("./data/d.txt",function(err,data){
        resolve(data)
    })
})
// 注意: promise中的异步任务是在new创建是直接开始执行的, then函数只是读取异步结果, 而不是开始执行异步请求
// Promise 中提供了两个类方法:  all   race
// 把多个promise对象合并成一个, 参数是数组,数组中放多个promise示例, 返回一个新的promise对象
var mergeP = Promise.all([p1,p2,p3,p4])
// 使用合并后的对象调用then获取所有promise的结果, 是一个数组,数组中数据顺序和all参数顺序保持一致 (注意不是按照执行结束的先后顺序排列)
mergeP.then(function(dataArr){
    console.log(4, dataArr.join(""))
})
// all: 当合并的所有promise全部完成之后, 才会执行then回调,拿到所有结果
// race: 任意一个合并的promise任务完成, 立即调用then回调, 只能拿到这一个结果


// 类方法: 使用类名直接调用的方法   如: Promise.all()   Promise.race()
// 实例方法: 创建出来的对象调用的方法   如 :  p1.then(),  p1.catch()

// 总结: promise的两个主要用法
// 1, 解决多异步任务多层嵌套问题
// 2, 解决多异步任务并发问题

3.Promise的原理

// promise的基本结构语法
// var promise = new Promise(function(resolve, reject){
//     setTimeout(() => {
//         var random = Math.random()
//         if(random < 0.7) resolve("成功的数据")
//         else reject("错误的信息")
//     }, 1000);
// })
// promise.then(function(data){ 
//     console.log(data) 
// }, function(err){ 
//     console.log(err)
// })

// 下边, 我们自己写一个类MyPromise, 模拟实现它的构造函数和then方法
// 自定义类的构造函数实现
function MyPromise(callback){
    console.log(1, "执行了MyPromise的构造函数");
    // 每一个promise对象内部,都会有一个状态信息, 有三个可能值
    // pending 状态 表示等待状态, promise对象的默认状态, 
    // resolve 状态 表示成功状态, 当调用了resolve函数时,状态变成成功状态
    // reject 状态 表示失败状态, 当调用了reject函数时,状态变成失败状态
    // 注意: 状态值只能变化一次,一旦变更为成功或失败状态,则会一致保持这个状态 
    this.state = "pending";  // 初始化状态
    console.log("out", this)  // promise对象
    // myResolve和myReject是在外部调用的,所以函数中this指向并不是当前promise对象
    let self = this
    // 定义成功时的回调函数
    function myResolve(data){
        console.log(3, data, this, self) // Object [global] , promise
        self.state = "resolve" // 修改状态值为成功
        self.value = data; // 成功时value属性记录成功数据
        self.success(data) // 成功时,调用then函数中成功回调
    }
    // 定义失败时的回调函数
    function myReject(err){
        console.log(3, err, this, self)
        self.state = "reject" // 修改状态值为失败
        self.msg = err;  // 失败时用msg属性记录失败信息
        self.fail(err) // 失败时,调用then函数中失败回调
    }

    // MyPromise在创建对象时,其回调函数会直接执行, 所以在构造函数中直接调用, 并传入成功和失败状态对应的函数
    callback(myResolve, myReject);
}
// then方法是promise对象调用的方法,所以定义到构造函数原型中
MyPromise.prototype.then = function(success, fail=()=>{}){
    // 由于then函数可以在任意时刻调用, 所以调用then时,promise状态值不确定
    if(this.state == "pending"){
        // 说明此时异步任务还未结束, 还不能调用success或fail, 此时可以把success和fail这个回调函数传入this这个promise对象, 在异步任务结束后调用
        this.success = success;
        this.fail = fail;
    }
    if(this.state == "resolve"){
        // 如果当前状态是成功状态, 则调用then参数中的第一个成功回调
        success(this.value) // 参数传入成功数据
    }
    if(this.state == "reject"){
        // 如果当前状态是失败状态, 则调用then参数中的第二个失败回调
        fail(this.msg) // 参数传入失败信息
    }

    // then函数返回当前promise对象, 用于链式调用
    return this;
}


// 使用构造函数创建实例对象, 创建时,构造函数会执行
var p = new MyPromise(function(resolve, reject){
    console.log(2, '执行了myPeomise回调')
    setTimeout(() => {
        var random = Math.random()
        if(random < 0.7) resolve("成功的数据")
        else reject("错误的信息")
    }, 1000);
})
// 在异步任务结束前,调用then函数
p.then(function(data){
    console.log(4, data)
},function(err){
    console.log(4, err)
}).then(function(){})
// 在异步任务结束后, 调用then函数
setTimeout(() => {
    p.then(function(data){
        console.log(5, data)
    },function(err){
        console.log(5, err)
    })
}, 2000);

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值