本文首发于 vivo互联网技术 微信公众号
连接: https://mp.weixin.qq.com/s/Xz2bGaLxVL4xw1M2hb2nJQ
做者:Morrainbash
不少同窗在学习 Promise 时,知其然殊不知其因此然,对其中的用法理解不了。本系列文章由浅入深逐步实现 Promise,并结合流程图、实例以及动画进行演示,达到深入理解 Promise 用法的目的。微信
本系列文章有以下几个章节组成:异步
图解 Promise 实现原理(二)—— Promise 链式调用学习
图解 Promise 实现原理(三)—— Promise 原型方法实现动画
图解 Promise 实现原理(四)—— Promise 静态方法实现ui
上一节中,实现了 Promise 的基础版本:this
//极简的实现+链式调用+延迟机制+状态
class Promise {
callbacks = [];
state = 'pending';//增长状态
value = null;//保存结果
constructor(fn) {
fn(this._resolve.bind(this));
}
then(onFulfilled) {
if (this.state === 'pending') {//在resolve以前,跟以前逻辑同样,添加到callbacks中
this.callbacks.push(onFulfilled);
} else {//在resolve以后,直接执行回调,返回结果了
onFulfilled(this.value);
}
return this;
}
_resolve(value) {
this.state = 'fulfilled';//改变状态
this.value = value;//保存结果
this.callbacks.forEach(fn => fn(value));
}
}复制代码
//使用Promise
function getUserId(url) {
return new Promise(function (resolve) {
//异步请求
http.get(url, function (id) {
resolve(id)
})
})
}
getUserId('some_url').then(function (id) {
//do something
return getNameById(id);
}).then(function (name) {
//do something
return getCourseByName(name);
}).then(function (course) {
//do something
return getCourseDetailByCourse(course);
}).then(function (courseDetail) {
//do something
});复制代码
真正的链式 Promise 是指在当前 Promise 达到 fulfilled 状态后,即开始进行下一个 Promise(后邻 Promise)。那么咱们如何衔接当前 Promise 和后邻 Promise 呢?(这是理解 Promise 的难点,咱们会经过动画演示这个过程)。url
先看下实现源码:spa
//完整的实现
class Promise {
callbacks = [];
state = 'pending';//增长状态
value = null;//保存结果
constructor(fn) {
fn(this._resolve.bind(this));
}
then(onFulfilled) {
return new Promise(resolve => {
this._handle({
onFulfilled: onFulfilled || null,
resolve: resolve
});
});
}
_handle(callback) {
if (this.state === 'pending') {
this.callbacks.push(callback);
return;
}
//若是then中没有传递任何东西
if (!callback.onFulfilled) {
callback.resolve(this.value);
return;
}
var ret = callback.onFulfilled(this.value);
callback.resolve(ret);
}
_resolve(value) {
this.state = 'fulfilled';//改变状态
this.value = value;//保存结果
this.callbacks.forEach(callback => this._handle(callback));
}
}复制代码
then 方法中,建立并返回了新的 Promise 实例,这是串行Promise的基础,是实现真正链式调用的根本。
then 方法传入的形参 onFulfilled 以及建立新 Promise 实例时传入的 resolve 放在一块儿,被push到当前 Promise 的 callbacks 队列中,这是衔接当前 Promise 和后邻 Promise 的关键所在。
根据规范,onFulfilled 是能够为空的,为空时不调用 onFulfilled。
看下动画演示:
(Promise 链式调用演示动画)
当第一个 Promise 成功时,resolve 方法将其状态置为 fulfilled ,并保存 resolve 带过来的value。而后取出 callbacks 中的对象,执行当前 Promise的 onFulfilled,返回值经过调用第二个 Promise 的 resolve 方法,传递给第二个 Promise。动画演示以下:
(Promise 链式调用 fulfilled)
为了真实的看到链式调用的过程,我写一个mockAjax函数,用来模拟异步请求:
/**
* 模拟异步请求
* @param {*} url 请求的URL
* @param {*} s 指定该请求的耗时,即多久以后请求会返回。单位秒
* @param {*} callback 请求返回后的回调函数
*/
const mockAjax = (url, s, callback) => {
setTimeout(() => {
callback(url + '异步请求耗时' + s + '秒');
}, 1000 * s)
}复制代码
//Demo1
new Promise(resolve => {
mockAjax('getUserId', 1, function (result) {
resolve(result);
})
}).then(result => {
console.log(result);
})复制代码
执行结果以下:
[Promse-1]:constructor
[Promse-1]:then
[Promse-2]:constructor
[Promse-1]:_handle state= pending
[Promse-1]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
=> Promise { callbacks: [], name: 'Promse-2', state: 'pending', value: null }
[Promse-1]:_resolve
[Promse-1]:_resolve value= getUserId异步请求耗时1秒
[Promse-1]:_handle state= fulfilled
getUserId异步请求耗时1秒
[Promse-2]:_resolve
[Promse-2]:_resolve value= undefined复制代码
构造 Promise-1 实例,当即执行 mackAjax('getUserId',callback);
调用 Promise-1 的 then 方法,注册 Promise-1 的 onFulfilled 函数。
then 函数内部构造了一个新的 Promise实例:Promise-2。当即执行 Promise-1 的 _handle方法。
此时 Promise-1 仍是pending的状态。
Promise-1._handle 中就把注册在 Promise-1 的 onFulfilled 和 Promise-2 的 resolve 保存在 Promise-1 内部的 callbacks。
至此当前线程执行结束。返回的是 Promise-2 的 Promise实例。
1s后,异步请求返回,要改变 Promise-1 的状态和结果,执行 resolve(result)。
Promise-1 的值被改变,内容为异步请求返回的结果:"getUserId异步请求耗时1s"。
Promise-1 的状态变成 fulfilled。
Promise-1 的 onFulfilled 被执行,打印出了"getUserId异步请求耗时1秒"。
而后再调用 Promise-2.resolve。
改变 Promise-2 的值和状态,由于 Promise-1 的 onFulfilled 没有返回值,因此 Promise-2的值为undefined。
上例中,若是把异步的请求改为同步会是什么的效果?
new Promise(resolve => {
resolve('getUserId同步请求');
}).then(result => {
console.log(result);
});
//打印日志
[Promse-1]:constructor
[Promse-1]:_resolve
[Promse-1]:_resolve value= getUserId同步请求
[Promse-1]:then
[Promse-2]:constructor
[Promse-1]:_handle state= fulfilled
getUserId同步请求
[Promse-2]:_resolve
[Promse-2]:_resolve value= undefined
=> Promise {
callbacks: [],
name: 'Promse-2',
state: 'fulfilled',
value: undefined }复制代码
执行当前 Promise 的 onFulfilled 时,返回值经过调用第二个 Promise 的 resolve 方法,传递给第二个 Promise,做为第二个 Promise 的值。因而咱们考虑以下Demo:
//Demo2
new Promise(resolve => {
mockAjax('getUserId', 1, function (result) {
resolve(result);
})
}).then(result => {
console.log(result);
//对result进行第一层加工
let exResult = '前缀:' + result;
return exResult;
}).then(exResult => {
console.log(exResult);
});复制代码
咱们加了一层 then,来看下执行的结果:
[Promse-1]:constructor
[Promse-1]:then
[Promse-2]:constructor
[Promse-1]:_handle state= pending
[Promse-1]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
[Promse-2]:then
[Promse-3]:constructor
[Promse-2]:_handle state= pending
[Promse-2]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
=> Promise { callbacks: [], name: 'Promse-3', state: 'pending', value: null }
[Promse-1]:_resolve
[Promse-1]:_resolve value= getUserId异步请求耗时1秒
[Promse-1]:_handle state= fulfilled
getUserId异步请求耗时1秒
[Promse-2]:_resolve
[Promse-2]:_resolve value= 前缀:getUserId异步请求耗时1秒
[Promse-2]:_handle state= fulfilled
前缀:getUserId异步请求耗时1秒
[Promse-3]:_resolve
[Promse-3]:_resolve value= undefined:复制代码
咱们很容易发现,上述 Demo3 中只有第一个是异步请求,后面都是同步的,咱们彻底没有必要这么链式的实现。以下同样能获得咱们想要的三个结果: 分别打印出来的值。
//等价于 Demo3
new Promise(resolve => {
mockAjax('getUserId', 1, function (result) {
resolve(result);
})
}).then(result => {
console.log(result);
//对result进行第一层加工
let exResult = '前缀:' + result;
console.log(exResult);
let finalResult = exResult + ':后缀';
console.log(finalResult);
});复制代码
刚才演示的都是 onFulfilled 返回值是 value 的状况,若是是一个 Promise 呢?是否是就能够经过 onFulfilled,由使用 Promise 的开发者决定后续 Promise 的状态。
因而在 _resolve 中增长对前一个 Promise onFulfilled 返回值的判断:
_resolve(value) {
if (value && (typeof value === 'object' || typeof value === 'function')) {
var then = value.then;
if (typeof then === 'function') {
then.call(value, this._resolve.bind(this));
return;
}
}
this.state = 'fulfilled';//改变状态
this.value = value;//保存结果
this.callbacks.forEach(callback => this._handle(callback));
}复制代码
//Demo4
const pUserId = new Promise(resolve => {
mockAjax('getUserId', 1, function (result) {
resolve(result);
})
})
const pUserName = new Promise(resolve => {
mockAjax('getUserName', 2, function (result) {
resolve(result);
})
})
pUserId.then(id => {
console.log(id)
return pUserName
}).then(name => {
console.log(name)
})复制代码
执行的结果以下:
[Promse-1]:constructor
[Promse-2]:constructor
[Promse-1]:then
[Promse-3]:constructor
[Promse-1]:_handle state= pending
[Promse-1]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
[Promse-3]:then
[Promse-4]:constructor
[Promse-3]:_handle state= pending
[Promse-3]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
=> Promise { callbacks: [], name: 'Promse-4', state: 'pending', value: null }
[Promse-1]:_resolve
[Promse-1]:_resolve value= getUserId异步请求耗时1秒
[Promse-1]:_handle state= fulfilled
getUserId异步请求耗时1秒
[Promse-3]:_resolve
[Promse-3]:_resolve value= Promise { callbacks: [], name: 'Promse-2', state: 'pending', value: null }
[Promse-2]:then
[Promse-5]:constructor
[Promse-2]:_handle state= pending
[Promse-2]:_handle callbacks= [ { onFulfilled: [Function], resolve: [Function] } ]
[Promse-2]:_resolve
[Promse-2]:_resolve value= getUserName异步请求耗时2秒
[Promse-2]:_handle state= fulfilled
[Promse-3]:_resolve
[Promse-3]:_resolve value= getUserName异步请求耗时2秒
[Promse-3]:_handle state= fulfilled
getUserName异步请求耗时2秒
[Promse-4]:_resolve
[Promse-4]:_resolve value= undefined
[Promse-5]:_resolve
[Promse-5]:_resolve value= undefined复制代码
(Promise 真正的链式调用)
至此,就实现了 Promise 链式调用的所有内容。链式调用是 Promise 难点,更是重点。必定要经过实例还有动画,深入体会。下一节介绍 Promise 其它原型方法的实现。
更多内容敬请关注 vivo 互联网技术 微信公众号
注:转载文章请先与微信号:Labs2020 联系。