ES6中Promise的一点理解
在了解Promise
之前我们需要去了解一下JS的线程机制
其实我们在前后端交互的时候经常会遇到异步处理,函数的回调的情况,现在的前端程序员基本上都会使用ES6
的语法,其中就是包含了对Promise
函数对象的使用,我们今天也来探讨一下Promise
方向
我们主要从以下几个方面去讨论Promise
:
Promise
是什么- 为什么使用
Promise
- 如何使用
Promise
- 如何自定义实现
Promise
Promise是什么
基本解释
- 抽象含义:
Promise
是JS
中进行异步编程时提出的新的解决方案,旧方法就是使用纯回调的方式 - 具体表达:
(1)语法:Promise
是一个构造函数对象
(2)功能:Promise
对象一般用来封装一个异步操作并可以获取其值 - 官方描述
Promise 对象用于表示一个异步操作的最终完成 (或失败), 及其结果值. - 包含的状态
三种状态:pending
,resolve
,reject
pending -> resolve
pending -> reject
近一步理解
所有Promise
的实现都离不开Promise/A+规范
在每一个Promise
中,如果状态发生了变化,那么只会变化一次。无论是成功还是失败都有一个返回结果。
术语
Promise
一个拥有then
方法的对象或函数,其行为符合Promises/A+
规范thenable
一个定义了then
方法的对象或函数,也可视作 “拥有then
方法”- 值(
value
) 指任何JavaScript
的合法值(包括undefined
,thenable
和promise
) - 异常(
exception
) 使用throw
语句抛出的一个值 - 据因(
reason
) 表示一个promise
的拒绝原因。
我们一般将成功返回的结果称为value
,将失败返回的结果称为reason
。
Promise的运行过程
@flowstart
st=>start: new Promise()【pending】
e=>end: 新promise对象
op1=>operation: 执行异步操作
op2=>operation: 返回promise对象(resolve)【resolved】
op3=>operation: 返回promise对象(reject)【rejected】
op4=>operation: thenable,回调onResolved【resolved】
op5=>operation: thenable,回调onRejected【rejected】
cond=>condition: 成功还是失败?
st(right)->op1(right)->cond
cond(yes)->op2->op4->e
cond(no)->op3->op5->e
@flowend
这个就是promise
的运行流程,当构建一个promise
对象执行异步操作后,分为两种可能
- 成功了,执行
resolve()
,pending -> resolved
。返回promise
对象、值,在thenable
中回调onResolved
函数,最后返回一个新的promise
对象 - 失败了,执行
reject()
,pending -> rejeccted
。返回promise
对象、值,在thenable
中回调onResolved
函数(也可以使用catch
接受exception
),最后返回一个新的promise
对象
为什么使用Promise
纯回调函数的嵌套使用很糟糕
function doSth() {}
function successCallback(res) {}
function failureCallbacl(err) {}
// 使用纯回调函数的方式,进行调用上述两个方法
doSomeThingAsync(doSth, successCallback, failureCallbacl) {}
// 使用Promise的方式
const promise = doSomeThingAsync(doSth)
setTimeout(() => { // 模拟异步操作
promise.then(successCallback, failureCallbacl)
})
这种方式从语义上面:使用Promise
的方式更加具有可读性,可以从函数执行的角度分许,我们是先执行同步函数doSth
,再去执行回调函数。使用纯回调的方式执行,并不能比较直观的看出来,函数执行的进程。
关于回调函数的指定,纯回调函数需要在启动异步任务之前就定义好,而Promise
的方式是:启动异步任务 => 返回Promise
对象 => 给Promise
对象绑定回调函数
同时,还有一个比较熟悉的概念:回调地狱问题。
doSomeThingOne(function(resOne) {
doSomeThingTwo(function(resOne, function(resTwo){
doSomeThingThree(function(resTwo, function(resThree) {
console.log('final result' + resThree)
}))
}))
})
上述的代码有一个特点就是,下一个函数的执行以来与上一个函数执行返回的结果,它是串连执行的。可以看到仅有三个回调的时候,里面没有逻辑代码已经看起来很难受,当有多重回调嵌套的时候,会使得代码显示的非常糟糕。我们使用promise
进行链式调用就可以解决毁掉地狱的问题。
doSomeThingOne().then( resOne => doSomeThingTwo(resOne)
}).then(resTwo => {
doSomeThingThree( resThree => {
console.log('final result' + resThree)
} )
})
这样我们就可以清楚的看到他们之间的区别。
拓展:我们使用Promise解决回调地狱的问题,还有更好的办法么?
// async/await
function getUserList() {} // 获取服务器端的用户列表,当成一个异步函数
async function _getUserList() {// 定义一个本地私有获取用户列表的方法
try {
const result = await getUserList() // 我们可以使用这种方式获取异步函数回调结果
const data = res.data
} cathc (err) {
console.log(err)
}
}
使用async/await
的方式,直接取消回调函数的使用,也是新增ES
特性,是终极解决方案。
如何使用Promise
API
Promise的构造函数
Promise (excutor) {}
(1) excutor函数: 执行器 (resolve, reject) => {}
(2) resolve函数: 内部定义成功时,我们调用的函数 value => {}
(3) reject函数: 内部定义失败时,我们调用的函数 reason => {}
说明:excutor 会在Promise内部立即同步执行回调,即异步操作在执行器中的函数中执行
方法
原型上的方法,一般在创建实例对象时候使用
Promise.prototype.then()
Promise.prototype.catch()
Promise.prototype.finally()
Promise.prototype.allSettled()
函数对象上的方法,静态方法可以在由内置的Promise对象直接使用。
Promise.all()
Promise.race()
Promise.reject()
Promise.resolve()
具体方法的含义不是本次的重点,详情请参照MDN-Promise
使用Promise API需要探讨的几个关键问题
Promise
的三种状态是如何改变的
resolve (value):【pending】-> 【resolved】,excutor回调执行成功时调用resolve函数 ,返回成功的值
reject (reason):【pending】-> 【rejected】,excutor回调执行失败时调用reject函数,返回失败的据因
throw exception:【pending】-> 【rejected】,在同步执行时抛出异常,直接走失败的回调一个
Promise
同时指定多个成功/失败回调函数,都会调用么
只要状态发生了改变,就会调用,可以看出状态的改变时promise触发的关键,也是触发promise的依据。Promise
状态的改变 和 执行回调函数的顺序
正常的逻辑是,返回了异步函数的结果 ->
改变状态 ->
执行thenable
,但如果这样
new Promise((resolve, reject) => {
setTimeout(() => {
resolve(1) // 后改变状态(同时指定数据),异步执行回调函数
}, 1000)
}).then( // then方法是立即执行的,它会先指定回调函数,如果当前的回调函数是异步执行的函数,会保存当前指定的回调函数
value => { console.log('value', value) },
reason => { console.log('reason', reason) }
)
这种方法是先改变状态再指定回调,但也可以使用同步:
new Promise((resolve, reject) => {
resolve(1) // 先改变状态(同时指定数据)
}).then( // then方法是立即执行的,它会后指定回调函数,异步执行回调函数
value => { console.log('value', value) },
reason => { console.log('reason', reason) }
)
也就是说,顺序是都有可能的,需要分情况讨论
正常情况下先指定回调再改变状态,但也可以先改变状态再指定回调
什么时候得到数据?如果先指定回调,那当状态发生改变时,回调函数就会调用,得到数据
如果先改变状态,那当指定回调时,回调函数就会调用,得到数据
Promise.then()
返回新的promise
的结果状态由什么决定
简述:由then()指定的回调函数执行的结果决定
new Promise((resolve, reject) => {
resolve(1)
// reject(1)
}).then(
value =>{
console.log('onResolved1', value)
// 当前的value是上一个Promise对象返回的值,如果没有定义,只声明那么value就是undefined 相当于 return undefined
// return 2 // value => 2
// return Promise.resolve(2) // value => 2
// return Promise.reject(2) // reason => 2
// throw error // reason => exception
},
reason => {
console.log('onRejected1', reason) // 1
}
).then(
value =>{
console.log('onResolved2', value) // undefined
},
reason => {
console.log('onRejected2', reason)
}
)
详述:
- 如果抛出异常,新的promise的状态由pending -> rejected, reason为抛出的异常
- 如果返回的是非promise的任意值,新的promise的状态由pending -> resolved, value为返回的成功值
- 如果返回的是一个新的promise,此promise执行的结果和状态决定新promise的状态,保持一致。
Promise
如何串连多个同步/异步任务?
使用then()方法,同步直接执行,异步操作需要包装到一个新的promise中作为结果返回
new Promise((resolve, reject) => {
resolve(1)
}).then(value =>{
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(value)
console.log('异步操作需要包装到一个promise对象中返回',value)
})
})
})
- 异常传透
- 当使用
promise
的then
的链式调用,可以在最后指定失败的回调函数 - 前面任何操作出了异常,都会传递到最后失败的回调中处理
new Promise((resolve, reject) => {
throw 1
}).then(
value => {},
reason => {
throw 1
}
).then(
value => {},
reason => {
throw 1
}
).catch(err){
console.log(err)
}
其实这个到底是传透还是穿透看你怎么理解,他是通过层层return
到底部那就是传透,如果所有的错误都调用catch
来捕获异常那就是穿透。不影响理解
- 中断
Promise
链 - 当使用
promise
的then
的链式调用,在中间中断,不会再调用后面的回调函数 - 办法:在回调函数中返回一个
pending
状态的promise
空对象
return new Promise(() => {}) // 返回一个空promise,默认状态是pending状态,指定一个pending状态的promise即可中断promise的链式调用(thenable)
new Promise((resolve, reject) => {
rejecct(1)
}).then(
value => {},
reason => { // 如果不写,就相当于抛出一个reject的reason
throw reason // reason = 1
}
).then(
value => {},
reason => {
throw reason // reason = 1
}
).catch(reason => {
console.log(reason)
return new Promise(() => {}) // 中断promise链,有其他状态 -> pending状态
}).then(
value => {},
reason => {
throw reason // reason = 1
}
)
如何自定义实现Promise
当我们了解了promise
相关比较复杂的知识点再去实现一个简单的promise
,那么对promise
会有一个更深的认识!
ps:这只是一个简单的实现,具体复杂的建议去看源码
基础版本
/* 自定义promise函数模块
我们使用es5的方法定义模块,如果使用es6语法这样就需要转义
IIFE - 立即执行函数
*/
(function(window) {
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
/*
Promise:构造函数
excutor:执行器(同步执行)
*/
function Promise(excutor) {
const _this = this;
/*
status:用来存储当前的状态,初始状态是pending
data:给promise对象指定一个用于存储结果数据的属性
callbacks:用于保存{onResolved(){},onRejected(){}}回调
*/
_this.status = PENDING;
_this.data = undefined;
_this.callbacks = [];
// 内部定义resolve函数
function resolve(value) {
// 判断当前的状态
if (_this.status !== PENDING) return;
/*
1.改变状态 pending -> resolved
2.保存value 将接受的value赋值给data
*/
_this.status = RESOLVED;
_this.data = value;
// 如果有待执行的callback函数,立即异步执行回调函数onResolved
if (_this.callbacks.length > 0) {
_this.callbacks.forEach(callbacksObj => {
setTimeout(() => {
//这种写法不太准确,因为setTimeOut属于宏队列元素,但是可以模拟当前需要的异步操作,我们的目的就是将回调函数放入执行stack中异步执行
callbacksObj.onResolved(value);
});
});
}
}
// 内部定义reject函数
function reject(reason) {
/*
1.改变状态 pending -> rejected
2.保存reason 将接受的reason赋值给data
*/
_this.status = REJECTED;
_this.data = reason;
// 如果有待执行的callback函数,立即异步执行回调函数onRejected
if (_this.callbacks.length > 0) {
_this.callbacks.forEach(callbacksObj => {
setTimeout(() => {
//这种写法不太准确,因为setTimeOut属于宏队列元素,但是可以模拟当前需要的异步操作,我们的目的就是将回调函数放入执行stack中异步执行
callbacksObj.onRejected(reason);
});
});
}
}
// 立即执行excutor
try {
excutor(resolve, reject);
} catch (error) {
// 如果执行器抛出异常,promise对象直接调用rejected
reject(error);
}
}
/*
Promise原型对象的then()
指定成功和失败的回调函数
返回一个新的promise对象
返回的promise的结果有onResolved和onRejected执行结果决定
*/
Promise.prototype.then = function(onResolved, onRejected) {
const _this = this;
// 指定回调函数的默认值(必须是函数)
onResolved = typeof onResolved === "function" ? onResolved : value => value;
onRejected = typeof onRejected === "function" ? onRejected : reason => { throw reason};
return new Promise((resolve, reject) => {
/*
执行指定的回调函数,根据执行的结果改变return的promise的状态
*/
function handle(callback) {
// 1.抛出异常
try {
const result = callback(_this.data);
if (result instanceof Promise) {
// 2.如果当前返回的是promise
result.then(resolve, reject);
} else {
// 3.如果返回的不是promise,为一个成功值
resolve(result);
}
} catch (error) {
// 4.如果返回的不是promise,为一个异常值
reject(error);
}
}
if (_this.status === RESOLVED) {
// 当前promise是resolved
setTimeout(() => {
handle(onResolved);
});
} else if (_this.status === REJECTED) {
// 当前promise是rejected
setTimeout(() => {
handle(onRejected);
});
} else {
// 当前promise是pending
// 将成功失败的回调函数保存到callbacks中缓存
_this.callbacks.push({
onResolved() {
handle(onResolved);
},
onRejected() {
handle(onRejected);
}
});
}
});
};
/*
Promise的原型对象的catch()
指定失败的回调函数,返回一个新的promise对象
*/
Promise.prototype.catch = function(onRejected) {
return this.then(undefined, onRejected);
};
/*
Promise函数对象resolve
返回一个指定结果
*/
Promise.resolve = function(value) {
// 返回成功/失败的promise
return new Promise((resolve, reject) => {
if (value instanceof Promise) {
value.then(resolve, reject);
} else {
resolve(value);
}
});
};
/*
Promise函数对象reject
返回一个失败结果
*/
Promise.reject = function(reason) {
return new Promise((resolve, reject) => {
reject(reason);
});
};
/*
Promise函数对象all
返回一个promise,只有当所有promise都成功时才成功,否则只要有失败就失败
*/
Promise.all = function(promises) {
const values = new Array(promises.length); // 用来保存所有成功value的数组
let resolvedCount = 0; // 用来保存成功的promise计数器,每执行成功一次就 ++ ,如果和需要执行的promises的长度相等则能够确定所有的都已经执行成功
return new Promise((resolve, reject) => {
// 遍历获取每一个promise的结果
promises.forEach((p, index) => {
Promise.resolve(p).then(
//使用Promise.resolve(p)是为了解决有可能传入promises数组的不是一个promise而是一个具体的值
// 我们在这个函数中需要明确,如何确保全部都成功,同时返回的成功的promise的顺序不会乱
value => {
resolvedCount++;
// p成功,将成功的value保存到values
values[index] = value;
// 如果全部成功,将return的promise改变成功
if (resolvedCount === promises.length) {
resolve(values);
}
},
// 只要一个失败了,return的promise就会失败
reason => {
reject(reason);
}
);
});
});
};
/*
Promise函数对象race
返回一个promise,其结果有第一个完成的promise决定
*/
Promise.race = function(promises) {
return new Promise((resolve, reject) => {
promises.forEach((p, index) => {
Promise.resolve(p).then(
value => {
resolve(value);
},
reason => {
reject(reason);
}
);
});
});
};
// 向外暴露 Promise 函数
window.Promise = Promise;
})(window);
class版本
/* ---Class版本--- */
/* 自定义promise函数模块
我们使用es5的方法定义模块,如果使用es6语法这样就需要转义
IIFE - 立即执行函数
*/
(function(window) {
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
/*
Promise:构造函数
excutor:执行器(同步执行)
*/
class Promise {
constructor(excutor) {
const _this = this;
/*
status:用来存储当前的状态,初始状态是pending
data:给promise对象指定一个用于存储结果数据的属性
callbacks:用于保存{onResolved(){},onRejected(){}}回调
*/
_this.status = PENDING;
_this.data = undefined;
_this.callbacks = [];
// 内部定义resolve函数
function resolve(value) {
// 判断当前的状态
if (_this.status !== PENDING) return;
/*
1.改变状态 pending -> resolved
2.保存value 将接受的value赋值给data
*/
_this.status = RESOLVED;
_this.data = value;
// 如果有待执行的callback函数,立即异步执行回调函数onResolved
if (_this.callbacks.length > 0) {
_this.callbacks.forEach(callbacksObj => {
setTimeout(() => {
//这种写法不太准确,因为setTimeOut属于宏队列元素,但是可以模拟当前需要的异步操作,我们的目的就是将回调函数放入执行stack中异步执行
callbacksObj.onResolved(value);
});
});
}
}
// 内部定义reject函数
function reject(reason) {
/*
1.改变状态 pending -> rejected
2.保存reason 将接受的reason赋值给data
*/
_this.status = REJECTED;
_this.data = reason;
// 如果有待执行的callback函数,立即异步执行回调函数onRejected
if (_this.callbacks.length > 0) {
_this.callbacks.forEach(callbacksObj => {
setTimeout(() => {
//这种写法不太准确,因为setTimeOut属于宏队列元素,但是可以模拟当前需要的异步操作,我们的目的就是将回调函数放入执行stack中异步执行
callbacksObj.onRejected(reason);
});
});
}
}
// 立即执行excutor
try {
excutor(resolve, reject);
} catch (error) {
// 如果执行器抛出异常,promise对象直接调用rejected
reject(error);
}
}
/*
Promise原型对象的then()
指定成功和失败的回调函数
返回一个新的promise对象
返回的promise的结果有onResolved和onRejected执行结果决定
*/
then(onResolved, onRejected) {
const _this = this;
// 指定回调函数的默认值(必须是函数)
onResolved = typeof onResolved === "function" ? onResolved : value => value;
onRejected = typeof onRejected === "function" ? onRejected : reason => { throw reason};
return new Promise((resolve, reject) => {
/*
执行指定的回调函数,根据执行的结果改变return的promise的状态
*/
function handle(callback) {
// 1.抛出异常
try {
const result = callback(_this.data);
if (result instanceof Promise) {
// 2.如果当前返回的是promise
result.then(resolve, reject);
} else {
// 3.如果返回的不是promise,为一个成功值
resolve(result);
}
} catch (error) {
// 4.如果返回的不是promise,为一个异常值
reject(error);
}
}
if (_this.status === RESOLVED) {
// 当前promise是resolved
setTimeout(() => {
handle(onResolved);
});
} else if (_this.status === REJECTED) {
// 当前promise是rejected
setTimeout(() => {
handle(onRejected);
});
} else {
// 当前promise是pending
// 将成功失败的回调函数保存到callbacks中缓存
_this.callbacks.push({
onResolved() {
handle(onResolved);
},
onRejected() {
handle(onRejected);
}
});
}
});
}
/*
Promise的原型对象的catch()
指定失败的回调函数,返回一个新的promise对象
*/
catch(onRejected) {
return this.then(undefined, onRejected);
}
/*
Promise函数对象resolve
返回一个指定结果
*/
static resolve(value) {
// 返回成功/失败的promise
return new Promise((resolve, reject) => {
if (value instanceof Promise) {
value.then(resolve, reject);
} else {
resolve(value);
}
});
}
/*
Promise函数对象reject
返回一个失败结果
*/
static reject(reason) {
return new Promise((resolve, reject) => {
reject(reason);
});
}
/*
Promise函数对象all
返回一个promise,只有当所有promise都成功时才成功,否则只要有失败就失败
*/
static all(promises) {
const values = new Array(promises.length); // 用来保存所有成功value的数组
let resolvedCount = 0; // 用来保存成功的promise计数器,每执行成功一次就 ++ ,如果和需要执行的promises的长度相等则能够确定所有的都已经执行成功
return new Promise((resolve, reject) => {
// 遍历获取每一个promise的结果
promises.forEach((p, index) => {
Promise.resolve(p).then(
//使用Promise.resolve(p)是为了解决有可能传入promises数组的不是一个promise而是一个具体的值
// 我们在这个函数中需要明确,如何确保全部都成功,同时返回的成功的promise的顺序不会乱
value => {
resolvedCount++;
// p成功,将成功的value保存到values
values[index] = value;
// 如果全部成功,将return的promise改变成功
if (resolvedCount === promises.length) {
resolve(values);
}
},
// 只要一个失败了,return的promise就会失败
reason => {
reject(reason);
}
);
});
});
}
/*
Promise函数对象race
返回一个promise,其结果有第一个完成的promise决定
*/
static race(promises) {
return new Promise((resolve, reject) => {
promises.forEach((p, index) => {
Promise.resolve(p).then(
value => {
resolve(value);
},
reason => {
reject(reason);
}
);
});
});
}
}
// 向外暴露 Promise 函数
window.Promise = Promise;
})(window);
其实手写的部分有一定的难度,表现在实现then方法,all方法,实现执行器方法上面
上述的js可以自行测试,满足基本的语法是没有问题的。主要就是为了能够更好的理解promise,如果你能够全部理解掌握promise的知识点,同时动手去撸一个promise。那么你离中高级前端又更近一步啦!