本文首发于 vivo互联网技能 微信大众号
链接: https://mp.weixin.qq.com/s/Xz2bGaLxVL4xw1M2hb2nJQ
作者:Morrain
许多同学在学习 Promise 时,知其然却不知其所以然,对其间的用法了解不了。本系列文章由浅入深逐渐完成 Promise,并结合流程图、实例以及动画进行演示,到达深入了解 Promise 用法的意图。
本系列文章有如下几个章节组成:
-
图解 Promise 完成原理(一)—— 根底完成
-
图解 Promise 完成原理(二)—— Promise 链式调用
-
图解 Promise 完成原理(三)—— Promise 原型办法完成
-
图解 Promise 完成原理(四)—— Promise 静态办法完成
一、前语
上一节中,完成了 Promise 的根底版别:
//极简的完成+链式调用+推迟机制+状况
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));
}
}
但链式调用,只是在 then 办法中 return 了 this,使得 Promise 实例能够屡次调用 then 办法,但由所以同一个实例,调用再屡次 then 也只能回来相同的一个成果,一般咱们期望的链式调用是这样的:
//运用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
});
每个 then 注册的 onFulfilled 都回来了不同的成果,层层递进,很明显在 then 办法中 return this 不能到达这个作用。引进实在的链式调用,then 回来的一定是一个新的Promise实例。
实在的链式 Promise 是指在当时 Promise 到达 fulfilled 状况后,即开端进行下一个 Promise(后邻 Promise)。那么咱们怎么联接当时 Promise 和后邻 Promise 呢?(这是了解 Promise 的难点,咱们会经过动画演示这个进程)。
二、链式调用的完成
先看下完成源码:
//完好的完成
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)
}
除此之外,我给 Promise 的源码加上了日志输出并添加了结构次序标识,能够清楚的看到结构以及履行进程:
//Demo1
new Promise(resolve => {
mockAjax('getUserId', 1, function (result) {
resolve(result);
})
}).then(result => {
console.log(result);
})
【Demo1 的源码】
履行成果如下:
[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);
});
【Demo2 的源码】
咱们加了一层 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:
链式调用能够无限的写下去,上一级 onFulfilled return 的值,会变成下一级 onFulfilled 的成果。能够参阅Demo3:
【Demo3 的源码】
咱们很简单发现,上述 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));
}
从代码上看,它是对 resolve 中的值作了一个特别的判别,判别 resolve 的值是否为 Promise实例,假如是 Promise 实例,那么就把当时 Promise 实例的状况改动接口从头注册到 resolve 的值对应的 Promise 的 onFulfilled 中,也就是说当时 Promise 实例的状况要依靠 resolve 的值的 Promise 实例的状况。
//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)
})
【Demo 4 的源码】
履行的成果如下:
[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 其它原型办法的完成。