图解 Promise 完成原理(二)—— Promise 链式调用

本文首发于 vivo互联网技能 微信大众号 
链接: https://mp.weixin.qq.com/s/Xz2bGaLxVL4xw1M2hb2nJQ
作者:Morrain

许多同学在学习 Promise 时,知其然却不知其所以然,对其间的用法了解不了。本系列文章由浅入深逐渐完成 Promise,并结合流程图、实例以及动画进行演示,到达深入了解 Promise 用法的意图。

本系列文章有如下几个章节组成:

  1. 图解 Promise 完成原理(一)—— 根底完成

  2. 图解 Promise 完成原理(二)—— Promise 链式调用

  3. 图解 Promise 完成原理(三)—— Promise 原型办法完成

  4. 图解 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 链式调用

实在的链式 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 链式调用

(Promise 链式调用演示动画)

当第一个 Promise 成功时,resolve 办法将其状况置为 fulfilled ,并保存 resolve 带过来的value。然后取出 callbacks 中的目标,履行当时 Promise的 onFulfilled,回来值经过调用第二个 Promise 的 resolve 办法,传递给第二个 Promise。动画演示如下:

图解 Promise 完成原理(二)—— 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

经过打印出来的日志,能够看到:

  1. 结构 Promise-1 实例,当即履行 mackAjax('getUserId',callback);

  2. 调用 Promise-1 的 then 办法,注册 Promise-1 的 onFulfilled 函数。

  3. then 函数内部结构了一个新的 Promise实例:Promise-2。当即履行 Promise-1 的 _handle办法。

  4. 此刻 Promise-1 仍是pending的状况。

  5. Promise-1._handle 中就把注册在 Promise-1 的 onFulfilled 和 Promise-2 的 resolve 保存在 Promise-1 内部的 callbacks。

  6. 至此当时线程履行完毕。回来的是 Promise-2 的 Promise实例。

  7. 1s后,异步恳求回来,要改动 Promise-1 的状况和成果,履行 resolve(result)。

  8. Promise-1 的值被改动,内容为异步恳求回来的成果:"getUserId异步恳求耗时1s"。

  9. Promise-1 的状况变成 fulfilled。

  10. Promise-1 的 onFulfilled 被履行,打印出了"getUserId异步恳求耗时1秒"。

  11. 然后再调用 Promise-2.resolve。

  12. 改动 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 实例的状况。

图解 Promise 完成原理(二)—— 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 链式调用的全部内容。链式调用是 Promise 难点,更是要点。一定要经过实例还有动画,深入领会。下一节介绍 Promise 其它原型办法的完成。