简单实现Promise
2019年08月18日 17:24:12 yhy1315 阅读数 3 标签: Promise实现JavaScript 更多
个人分类: JavaScript
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/yhy1315/article/details/99707014
简单实现Promise
Promise在我们平时写js中经常用到,(如果你没有经常用到,也许你该好好学习一下ES6了!)但只有知道其内部实现原理才能更好的使用它,所以我们今天来一起深入学习Promise!
了解Promise
学习一个东西,最好是从它的历史学起,为什么它会出现?为什么它更好?为什么它这样设计?保留这些问题,我们将一一解释。
不够优雅的callback
基于单线程js的开发都快把人搞秃了,想象一种情形:用户点击提交你页面表单,为了让用户有更好的体验,我们不跳转式提交表单,那么我们就要在JavaScript线程中提交。但是我们很穷,服务器的带宽只有1Mbps,这会导致什么?JavaScript在提交表单后苦苦等待服务器响应,但是由于我们网速太慢,无法及时获得相应,用户也不耐烦在页面瞎点(永远不要信任用户!),用户会发现所有页面相应都无法获得(主线程在处理相应!),那他就会放弃这家网站!
没错,单线程就会出现这些事情,后来聪明的工程师想出了一个办法,通过事件机制来回调异步操作!那么,上面情况就轻而易举的解决了!我们只需要监听ajax的onMessage方法,并在方法中调用我们的callback函数即可!
回调虽然很好,但是有一个比较严重的问题!看下面例子。
ajax.onMessage(function(msg){
let ajax2 = Ajax.get("www.domain/controller.com")
ajax2.onMessage(function(msg){
let ajax3 = Ajax.get(".....")
ajax3.onMessage(function(msg){
//....
})
})})
哦!回调越来越多,我们好像无法掌握这串代码了!这就是回调不足之处!回调地狱。
拯救世界的Promise
那么有没有一种方法,能让我们想同步一样解决异步问题呢?聪明的工程师又想到一种方法,我们应该将这些回调封装成一个函数,供我们随时使用,而且为了解决嵌套的可读性差,我们应该使用链式调用(相信我,链式真的比嵌套好)!
来看一下Promise的使用:
//Ajax全为抽象ajax操作,不要将它带入任何实际操作!//我们封装了一个ajax在Promise中的操作let ajaxToPromise = url=>{
return new Promise((resolve,reject)=>{
//为了方便在本地测试,我们不用ajax请求,我们改用setTimeOut方法,相信聪明的你一定能理解
setTimeout(()=>{
resolve()
console.log("延迟 " + url + " ms的操作被完成!")
},url);
})}//封装完成 我们看看使用时能为我们提供什么便利let url1,url2,url3;ajaxToPromise(1000).then(()=>{return ajaxToPromise(1000)}).then(()=>{return ajaxToPromise(1000)})
如果你运行,你会发现每隔1000ms就会有输出。
很清楚,我们通过我们封装的ajaxToPromise方法,一行解决了异步请求多次问题!接下来,我们将会知道Promise究竟在做什么?
new Promise( function(resolve, reject) {...} /* executor */ );
简单理解下:Promise构造函数仅仅接受函数作为参数,并在构造方法中立即调用该函数。以后Promise会作为该函数的代理,在该函数改变Promise状态值时,可以使用then()方法执行后续操作。
Promise的内部实现
如果你只对Promise的运行与使用感兴趣你可以跳过这部分,但是我会推荐任何人读它。
Promise内部使用回调函数调用then()方法注册的回调,在什么时候用?我们显式调用resolve()就会更改Promise状态,相应调用在then()中注册的回调。
如果你看不太懂,也没有关系,毕竟语言是很抽象的,我们直接上代码,不用担心,我们不会直接写出Promise全部代码,一点一点来才利于学习。
构造函数实现
希望大家都喜欢ES6的语法!(笑
class MyPromise{
/**
* 构造函数 立即执行callback函数
* @param {Function} callback
*/
constructor(callback) {
if(this.isFunction(callback)){
callback()
}else{
throw new Error("MyPromise 必须接受一个函数参数!")
}
}
/**
* 判断func是否为Function对象
* @param {Function} func
*/
isFunction(func){
return typeof func == "function"
}}
构造方法已经完成,但是我们注意力应该放在then()方法和resolve()方法。下面我们先考虑then()方法的实现。
then()方法实现
then()方法应该去注册执行回调函数,而本身不执行,选择让用户在callback方法中执行是更好的选择。那么我们注册的方法应该存放一下,选择队列存放吧!我们维护两个队列onFulfilledQueue与onRejectedQueue,我们创建一个方法initData(),其中初始化我们所有的成员变量。
class MyPromise{
constructor(callback) {
if(this.isFunction(callback)){
this.initData()//初始化变量
callback()
}else{
throw new Error("MyPromise 必须接受一个函数参数!")
}
}
//...上面代码这里省略
/**
* 初始化MyPromise所谓成员变量
*/
initData(){
this.callback = null;//用户传入构造的callback
this.onFulfilledQueue = []//注册的成功回调
this.onRejectedQueue = []//注册的失败回调
this.value = null;//要返回的成功值
this.error = null;//要返回的失败值
}}
then()方法,接受最多两个参数onFulfilled,onRejected分别应当在状态是成功与失败时调用,那么我们先引入状态!
// 定义Promise的三种状态常量// 把下面代码加入你的initData()中this.PENDING = 0//进行中this.FULFILLED = 1//已完成this.REJECTED = -1//以失败this.status = this.PENDING//现在状态 默认为进行中
ok,到这一步,我们封装了成员变量初始化,为我们的Mypromise引入了状态机制,then()的雏形可以写出来了。
/**
* then方法最多接受两个函数类型参数
* 如果此时MyPromise状态为完成将会执行onFulfilled
* 若为错误执行onRejected
* @param {Function} onFulfilled
* @param {Function} onRejected
*/
then(onFulfilled, onRejected) {
if(this.status == this.PENDING){//未完成时 加入队列
if(this.isFunction(onFulfilled))
this.onFulfilledQueue.push(onFulfilled)
if(this.isFunction(onRejected))
this.onRejectedQueue.push(onRejected)
}else if(this.status == this.FULFILLED&& this.isFunction(onFulfilled)){
onFulfilled(this.value)//如果这时候状态改变为已完成 我们直接调用onFulfilled
}else if(this.status==this.REJECTED&&this.isFunction(onRejected)){
onRejected(this.error)//这里同上面
}
}
基本雏形出来了,但是我们还要考虑,因为then()要支持链式调用,所以我们应该在最后返回MyPromise的一个实例。
但是又出现了新的问题,我们返回的MyPromise实例什么时候改变状态?我们应该依赖于上个MyPromise的then()方法的返回值,所以我们要大改代码
继续完善代码
then(onFulfilled, onRejected) {
return new MyPromise((onFulfilledNext, onRejectedNext) => {
let fulfilled = value => {
try {
if (!this.isFunction(onFulfilled)) {
onFulfilledNext(value)
} else {
let res = onFulfilled(value);
if (res instanceof MyPromise) {
res.then(onFulfilledNext, onRejectedNext);
} else {
onFulfilledNext(res)
}
}
} catch (error) {
onRejectedNext(error)
}
}
let rejected = error => {
try {
if (!this.isFunction(onRejected)) {
onRejectedNext(error)
} else {
let res = onRejected(error);
if (res instanceof MyPromise) {
// 如果当前回调函数返回MyPromise对象,必须等待其状态改变后在执行下一个回调
res.then(onFulfilledNext, onRejectedNext)
} else {
//否则会将返回结果直接作为参数,传入下一个then的回调函数,并立即执行下一个then的回调函数
onFulfilledNext(res)
}
}
} catch (err) {
// 如果函数执行出错,新的Promise对象的状态为失败
onRejectedNext(err)
}
}
switch (this.status) {
// 当状态为pending时,将then方法回调函数假如执行队列等待执行
case this.PENDING:
this.onFulfilledQueue.push(fulfilled)
this.onRejectedQueue.push(rejected)
break
// 当状态已经改变时,立即执行对应的回调函数
case this.FULFILLED:
fulfilled(this.value)
break
case this.REJECTED:
rejected(this.error)
break
}
});
}
现在代码很完美,我们then中返回的MyPromise依赖于上个then的调用及返回值了。
实现_resolve与_reject方法
这一节就比较简单,我们要干的事情,就是判断状态,并全部调用队列中的函数。
_resolve(value) {
if (this.status != this.PENDING) return;
this.status = this.FULFILLED;
this.value = value;
const run = () => {
while (this.onFulfilledQueue.length != 0) {
let cb = this.onFulfilledQueue.shift()
cb(this.value)
}
}
setTimeout(run, 0//要在MyPromise中支持同步操作 也就是resolve()之后操作先于then()中操作
//不太懂的同学可以看看我的有一篇详细讲解js中的event loop
}
_reject(error) {
if (this.status != this.PENDING) return
this.status = this.REJECTED
this.error = error
const run = () => {
while (this.onRejectedQueue.length != 0) {
let cb = this.onRejectedQueue.shift()
cb(this.error)
}
}
setTimeout(run, 0)
}
catch实现
catch(onRejected){
console.log(onRejected)
return this.then(null,onRejected)
}
catch()方法本身只是then()方法的一种调用。