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/awaitfunction 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的reasonthrow 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:用来存储当前的状态,初始状态是pendingdata:给promise对象指定一个用于存储结果数据的属性callbacks:用于保存{onResolved(){},onRejected(){}}回调*/_this.status = PENDING;_this.data = undefined;_this.callbacks = [];// 内部定义resolve函数function resolve(value) {// 判断当前的状态if (_this.status !== PENDING) return;/*1.改变状态 pending -> resolved2.保存value 将接受的value赋值给data*/_this.status = RESOLVED;_this.data = value;// 如果有待执行的callback函数,立即异步执行回调函数onResolvedif (_this.callbacks.length > 0) {_this.callbacks.forEach(callbacksObj => {setTimeout(() => {//这种写法不太准确,因为setTimeOut属于宏队列元素,但是可以模拟当前需要的异步操作,我们的目的就是将回调函数放入执行stack中异步执行callbacksObj.onResolved(value);});});}}// 内部定义reject函数function reject(reason) {/*1.改变状态 pending -> rejected2.保存reason 将接受的reason赋值给data*/_this.status = REJECTED;_this.data = reason;// 如果有待执行的callback函数,立即异步执行回调函数onRejectedif (_this.callbacks.length > 0) {_this.callbacks.forEach(callbacksObj => {setTimeout(() => {//这种写法不太准确,因为setTimeOut属于宏队列元素,但是可以模拟当前需要的异步操作,我们的目的就是将回调函数放入执行stack中异步执行callbacksObj.onRejected(reason);});});}}// 立即执行excutortry {excutor(resolve, reject);} catch (error) {// 如果执行器抛出异常,promise对象直接调用rejectedreject(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.如果当前返回的是promiseresult.then(resolve, reject);} else {// 3.如果返回的不是promise,为一个成功值resolve(result);}} catch (error) {// 4.如果返回的不是promise,为一个异常值reject(error);}}if (_this.status === RESOLVED) {// 当前promise是resolvedsetTimeout(() => {handle(onResolved);});} else if (_this.status === REJECTED) {// 当前promise是rejectedsetTimeout(() => {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) {// 返回成功/失败的promisereturn 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保存到valuesvalues[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:用来存储当前的状态,初始状态是pendingdata:给promise对象指定一个用于存储结果数据的属性callbacks:用于保存{onResolved(){},onRejected(){}}回调*/_this.status = PENDING;_this.data = undefined;_this.callbacks = [];// 内部定义resolve函数function resolve(value) {// 判断当前的状态if (_this.status !== PENDING) return;/*1.改变状态 pending -> resolved2.保存value 将接受的value赋值给data*/_this.status = RESOLVED;_this.data = value;// 如果有待执行的callback函数,立即异步执行回调函数onResolvedif (_this.callbacks.length > 0) {_this.callbacks.forEach(callbacksObj => {setTimeout(() => {//这种写法不太准确,因为setTimeOut属于宏队列元素,但是可以模拟当前需要的异步操作,我们的目的就是将回调函数放入执行stack中异步执行callbacksObj.onResolved(value);});});}}// 内部定义reject函数function reject(reason) {/*1.改变状态 pending -> rejected2.保存reason 将接受的reason赋值给data*/_this.status = REJECTED;_this.data = reason;// 如果有待执行的callback函数,立即异步执行回调函数onRejectedif (_this.callbacks.length > 0) {_this.callbacks.forEach(callbacksObj => {setTimeout(() => {//这种写法不太准确,因为setTimeOut属于宏队列元素,但是可以模拟当前需要的异步操作,我们的目的就是将回调函数放入执行stack中异步执行callbacksObj.onRejected(reason);});});}}// 立即执行excutortry {excutor(resolve, reject);} catch (error) {// 如果执行器抛出异常,promise对象直接调用rejectedreject(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.如果当前返回的是promiseresult.then(resolve, reject);} else {// 3.如果返回的不是promise,为一个成功值resolve(result);}} catch (error) {// 4.如果返回的不是promise,为一个异常值reject(error);}}if (_this.status === RESOLVED) {// 当前promise是resolvedsetTimeout(() => {handle(onResolved);});} else if (_this.status === REJECTED) {// 当前promise是rejectedsetTimeout(() => {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) {// 返回成功/失败的promisereturn 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保存到valuesvalues[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。那么你离中高级前端又更近一步啦!
