零、目录
- Promise 介绍
- Promise的简单使用例子
- then 方法在Promise异步和同步状态下执行时的具体顺序
- promise的两种使用方式以及具体应用场景
- Promise 的代码实现仿写
一、Promise 介绍
Promise 是在 es6 提供给开发者的一种用于异步编程的解决方案, 通过 Promise 可以更为方便且优雅的解决复杂异步问题, 同时 Promise通过代码的纵向拓展解决了回调地狱的问题。
1.Promise 内部构造
Promise对象的内部存在但不局限于以下四个成员(以下四个最为核心, 其中resolve, reject两个接口并不直接挂载在Promise的对象上, 而是被暴露给了执行器函数)如图 1.1
(图1.1 Promise 内部构造简图)
(1) PromiseState: 用于记录 Promise 对象的状态, 它只有以下三种类型的值
- pending:未执行resolve 或 reject 方法(未确定Promise此时的状态)
- fulfilled: 执行器 执行了resolve 方法
- rejected:执行器 执行了reject方法, 或者在执行器内部抛出了异常
(2) PromiseResult: 记录Promise的状态发生跃迁之后, resolve 和 reject 所传递的参数值
(3) resolve: 一个触发函数, 这是用于将该 promise 对象状态迁移成 fulfilled状态的一个接口, resolve函数所接收的参数, 会成为该promiseState的值
(4) reject: 同resolve一样, 是一个用于跃迁状态的接口, 它会将Promise的状态变更为 rejected, 同时它所接收到的参数, 会被保存在PromiseState里
注: 对于一个 Promise 对象而言, 它仅会发生一次状态跃迁,而且其状态只能从 pending迁移到 fullfiled 或者 rejected状态 , 如图 1.2。
(图1.2 Promise 状态跃迁图)
Promise 在初始化的时候,会接收一个函数,在这里我们称之为执行器函数 executor, Promise 通过将 resolve和reject这两个用于改变状态的接口传递给executor函数。 这样, 我们就 可以在 executor中调用这两个接口来改变Promise状态。
Promise 一经定义, 就会立即执行 executor 函数,(如果 executor 函数是个异步函数的话,后续动作则会被推送到执行队列延后执行, 具体流程在后边细谈),Promise在 executor 函数中的resolve、reject函数被执行或者抛出异常时, 即刻发生状态跃迁。
这里值得注意的两点是
- 即使resolve或者reject函数被调用了, executor 函数仍然会执行到其整个上下文结束,换句话说,整个 executor 函数并不会被resolve或是reject函数中断
- Promise状态由第一个执行的resolve或者reject来决定, 即当一个 executor 先执行了 resolve函数, 那么即使随后又执行了reject函数,其状态也不再会发生改变, 反之亦然。
2.then方法
Promise一般都伴随着 then方法来使用, 当Promise触发状态跃迁时, 触发状态跃迁的 resolve或reject函数所接收到的参数,会成为 PromiseResult的值, 并且这个值在之后会被作为参数传递给 then方法,then方法的返回值仍然是一个Promise对象。
(图2.1 Promise和then函数配合执行简图)
then方法接收一个参数为resolve 和 reject的函数, 当Promise状态跃迁为 fulfilled(rejected)时,后续就会执行then方法中的resolve(reject)函数,(这看起来有点像订阅发布(观察者)模式,当然其中也有所不同。 区别在于,当Promise在 executor 执行器为同步函数时的,then方法中的resolve和reject的触发并不是即刻生效的, 它会等到 js代码执行到 then方法的词法作用域时,才会触发。
二、Promise的简单使用例子
我们用以下一个简答的例子来举例 Promise的使用流程。
let promise = new Promise((resolve, reject) => {
// 用一个定时器, 模拟 ajax请求登录
setTimeout(() => {
resolve({
name: 'remiliko',
})
})
})
promise.then(data => {
console.log('welcome', data) // welcome remiliko
}, err => {
console.log('login err')
}
三、then 方法在Promise异步和同步状态下执行时的具体顺序
then方法实际执行的顺序 取决于Promise的 executor 是同步函数或是异步函数(这个问题等同于then方法执行在Promise的状态发生跃迁之前还是之后, 当然这两种情况都是可能发生的)。
1.promise状态跃迁先于then方法执行
当 executor 函数为同步函数时候,执行流程很简单, 如图 3.1所示
(图3.1 promise状态跃迁先于then方法执行)
当 executor 函数为同步函数时, Promise的执行顺序和普通代码块并没有太多区别, 就像流水线一样, 直到代码执行到then方法的词法作用域,才会触发。
2.Promise状态跃迁慢于then方法执行
在Promise之中, 维护了一个专门存储 then方法的队列(更具体来说存储的是then方法的resolve和reject参数),当程序执行到了then 方法的词法作用域的时候, Promise的状态仍然为 Pending状态, 那么此刻, 该then方法的resolve和reject都会被挂载到Promise身上的任务队列之中, 等到Promise状态一旦发生改变, 就立即执行, 而这也就是一个典型的订阅发布(观察者模型)模型, 具体流程如图3.2所示。
(图3.2 Promise状态跃迁晚于then方法执行)
四、promise的两种使用方式以及具体应用场景
我们通过具体应用场景的方式来阐释其对应的使用方法(应用场景仅仅为了恰当解释问题, 不一定是最优解法)
1.对同一Promise施加多个then方法
Promise支持对同一个Promise对象同时执行多个then函数,它的形态大抵如图4.1所示
(图4.1 对同一个Promise施加多个then方法)
由于这一特性的存在,使得我们可以很容易的实现广播的效果,例如
比如我们在处理一个登录事件, 当登陆事件触发时, 我们需要执行刷新购物车,刷新导航栏等业务代码,那么我们就可以利用这一特性, 模拟代码如下
let promise = new Promise((resolve, reject) => {
// 用一个定时器, 模拟 ajax请求登录
setTimeout(() => {
resolve({
name: 'remiliko',
})
})
})
promise.then(data => {
console.log('刷新导航栏', data) // 刷新导航栏 remiliko
}, err => {
console.log('login err')
})
promise.then(data => {
console.log('刷新购物车', data) // 刷新购物车 remiliko
}, err => {
console.log('login err')
})
promise.then(data => {
/* ...statement... */
}, err => {
console.log('login err')
})
2. promise的链式调用(也称作级联调用)
之所以能够链式调用then方法,是因为 Promise的then方法用一个Promise将其返回值封装了起来,这样then方法的返回值也会是一个Promise,自然可以执行then方法, then方法返回的promise的状态触发情况有如下以下几种
- pending : then方法所对应的promise对象,并未发生状态跃迁, 那么then方法会被挂起, 并不会执行
- fulfilled: then方法中无论是执行了resolve函数 还是reject函数, 如果没有抛出异常,或者显示的返回一个状态为rejected的Promise对象, 那么then方法返回的Promise状态都会是 fulfilled, 这个Promise对象对其使用then方法, 它会得到来自上一个then方法所返回的值, 如果上一个then方法没有返回值, 那么会得到一个PromiseResult为undefined的对象。
- rejected: then方法中的resolve或者reject函数显示的返回了一个状态为rejected的promise对象,或者抛出了异常
Promise链式调用的执行图大抵如图4.2所示
(图4.2 Promise的链式调用)
then方法选择调用resolve方法,还是reject方法, 主要取决于它所参照的Promise身上状态而言。而对then方法返回的Promise对象,再次执行then方法,依然会参照以上规则, 整个执行流程看起来有点像简化版的某种循环系统。
(有一个常用的技巧是,由于Promise内部对每个resolve和reject函数都用try catch包裹了起来,因此对于一个链式的promise队列,其具备异常穿透的特性, 我们可以不必对每一个then方法都进行错误判断,取而代之的是用一个catch函数放置在then队列的最末尾处, 这样无论异常出现在哪一个then方法之中都会被传递到最后catch方法之中)
Promise的链式调用通常会出现在阶段性请求的场景之中,我们拿以下一个简单的例子举例
比如,有的网站需要先登录用户,进而判断用户权限, 然后再去请求某些权限数据.....
同样的我们会使用setTimeout方法模拟AJAX请求,代码如下
let Promise = new Promise((resolve, reject) => {
setTimeout(() => {
// 登录用户, 并传入 id 信息
resolve({name:'remiliko', id: '23888'})
}, 1 * 1000);
}).then(data => {
// 提取出 id 信息, 去查询用户的购物车
let { id } = data
setTimeout(() => {
resolve({ commodity : [
{item: '1'},
{item: '2'}
]})
}, 1 * 1000)
}).then(data=> {
// 在这里将商品信息统一呈现到前端界面
}).catch(err => {
// 请求失败的同一处理
console.log(err)
})
五、Promise 的代码实现仿写
/* 这里仅仅实现最基础的功能 */
function mPromise(executor) {
if (!executor instanceof Function)
return console.warn('executor is no a function');
this.PromiseState = 'pending'; // 记录 Promise 状态
this.PromiseResult = null; // 用于存储 Promise 状态跃迁后, 得到的结果
executor(this.resolve.bind(this), this.rejected.bind(this));
}
// 用于将 Promise 状态变更为 fullfilled 的钥匙
mPromise.prototype.resolve = function (data) {
this.PromiseState = 'fulfilled';
this.PromiseResult = data;
if (this.chain) {
// 执行多任务请求
for (let i = 0, fns; (fns = this.chain[i++]); ) {
if (this.PromiseState == 'fulfilled') {
this.PromiseResult = fns.doResolve(this.PromiseResult);
}
}
}
};
mPromise.prototype.rejected = function (data) {
this.PromiseState = 'rejected';
this.PromiseResult = data;
if (this.chain) {
for (let i = 0, fns; (fns = this.chain[i++]); ) {
if (this.PromiseState == 'rejected') {
this.PromiseResult = fns.doReject(this.PromiseResult);
}
}
}
};
mPromise.prototype.then = function(doResolve, doReject) {
return new mPromise((resolve, reject) => {
// 用于判断 then 方法返回的结果是一个 promise 还是 其他值
let dispatch = (adapter) => {
try {
let res = adapter(this.PromiseResult)
if(res instanceof mPromise) {
// 由于需要考虑 promise是同步或者异步, 因此统一借助 then 方法来变更状态
res.then(v => resolve(v), r => reject(r))
} else {
// 对于普通对象
resolve(this.PromiseResult)
}
} catch(err) {
// 收集异常, 并改变 then方法返回的 Promise状态
reject(err)
}
}
if(this.PromiseState == 'fulfilled') {
// 为了保证 then 方法执行时机的统一都是异步执行
setTimeout(() => {
dispatch(doResolve)
})
}
else if(this.PromiseState == 'rejected') {
setTimeout(()=> {
dispatch(doReject)
})
}
// 当js执行到 then方法的词法作用域, 而Promise状态为pending, 将then方法挂载到执行队列
else {
let self = this
// 为了保证可以执行多个 then
if(!this.chain)
this.chain = []
this.chain.push({
doResolve: function() {
dispatch(doResolve)
},
doReject: function() {
dispatch(doReject)
}
})
}
})
}