手写Promise【拉勾:大前端高薪训练营】

osc_um3gbrdm 2020-11-10 12:53:27
bind


学习资料:拉勾课程《大前端高薪训练营》
阅读建议:文章较长,搭配文章的侧边栏目录进行食用,体验会更佳哦!

Promise对象是一个可以管理同步 / 异步操作运行结果的状态和数据的容器。它的核心逻辑就是外部操作合理地把它的运行结果状态和数据交由容器存储并管理,而后外部再从容器中取出该外部操作的状态和数据进行下一步操作。

本文接下来从以下手写Promise的五个关键认识开始探讨,最终完成手写Promise:

  • Promise容器的基本模样(容器的属性和行为)
  • Promise容器的构造方式(容器托管excutor函数的运行结果状态和数据)
  • 往Promise容器中写入数据(resolve、reject函数负责)
  • 从Promise容器中读取数据(then方法负责)
  • 实现then函数的链式调用

一:Promise容器的基本模样(容器的属性和行为)

1.容器管理的数据(属性)

  • 状态state:pending、resolved / fulfilled、rejected
  • 结果value:容器resolved / fulfilled状态下的数据
  • 拒因reason:容器rejected状态下的数据
  • onResolvedTodoList:pending状态时添加,容器resolved / fulfilled状态后的回调行为数组
  • onRejectedTodoList:pending状态时添加,容器rejected状态后的回调行为数组

2.容器数据的读写(行为)

  • 写操作:容器内部定义一个可以设置内部状态和数据的resolve和reject方法,以供外部为容器中写入数据(value / reason)。实际上,Promise容器对写操作有约束,它只允许初始化写操作,不允许更改写操作。
  • 读操作:容器内部定义一个可以读取内部状态和数据的then方法,以供外部读取容器中的状态和数据后触发相应的回调行为。

3.代码实现

class Container {

state = undefined;
value = undefined;
reason = undefined;
constructor(excutor) {
 // 构造容器 }
resolve = value => {
 // 写容器数据 }
reject = reason => {
 // 写容器数据 }
onResolvedTodoList = [];
onRejectedTodoList = [];
then(onResolved, onRejected) {
 // 读容器数据 }
}
Container.PENDING = 'pending';
Container.RESOLVED = 'resolved';
Container.REJECTED = 'rejected';

二:Promise容器实例的构造方式(容器托管excutor函数的运行结果状态和数据)

1.构造方法的参数函数excutor的定义

  • 哪里定义:容器外部定义
  • 函数职责:1是业务操作载体。2是把excutor运行后的状态和数据交给容器管理

容器外部对构造方法调用示例:

// 重点关注:构造参数excutor函数的定义,它定义了两个形参resolve和reject
const p1 = new Container((resolve, reject) => {

// 内部逻辑决定什么时候初始化容器数据,初始化什么数据
setTimeout(() => {

resolve(0)
})
})

2.构造方法的参数函数excutor的调用

  • 哪里调用:容器内部调用
  • 调用时机:构造容器实例时就调用

容器内部对构造方法的实现示例:

class Container {

state = undefined;
value = undefined;
reason = undefined;
// 重点关注:构造参数excutor函数的调用,它的调用时机以及实参传递。
constructor(excutor) {

try {

excutor(this.resolve, this.reject);
this.state = Container.PENDING;
} catch (e) {

this.reject(e)
}
}
resolve = value => {
 // 写容器数据 }
reject = reason => {
 // 写容器数据 }
then(onResolved, onRejected) {
 // 读容器数据 }
}
Container.PENDING = 'pending';
Container.RESOLVED = 'resolved';
Container.REJECTED = 'rejected';

三:往Promise容器中写入数据(resolve、reject函数负责)

1.resolve、reject函数的定义

  • 哪里定义:容器内部定义
  • 函数职责:把excutor运行后的状态和数据交给容器管理
  • 不允许更新容器状态和数据:如果容器状态不是pending就视为无效通知,啥也不做。
  • 处理状态订阅的回调数组:onResolvedTodoList 和 onRejectedTodoList数组中存储着订阅某一状态的回调函数,在初始化好容器管理的状态和数据后,就可以根据容器状态,按插入顺序把回调函数从回调数组中逐个取出执行。
  • 定义在容器内部:定义在容器内部,方便访问并设置容器状态和数据。
  • 定义为箭头函数:定义为箭头函数,调用者就不能通过任何方式改变其内部的this指向,即使使用call、apply、bind函数。

容器内部对resolve和reject的实现示例:

class Container {

constructor(excutor) {
}
resolve = value => {

if (this.state != Container.PENDING) return
this.status = Container.RESOLVED;
this.value = value;
while (this.onResolvedTodoList.length) this.onResolvedTodoList.shift()() // 取出第一个
}
reject = reason => {

if (this.state != Container.PENDING) return
this.status = Container.REJECTED;
this.reason = reason;
while (this.onRejectedTodoList.length) this.onRejectedTodoList.shift()()
}
then(onResolved, onRejected) {
}
}

2.resolve、reject函数的调用

  • 哪里调用:容器外部调用
  • 调用时机:支持同步、异步方式调用,异步调用时,表现为回调函数的调用。

容器外部对resolve和reject的调用示例:

const p1 = new Promise((resolve, reject) => {

// 同步方式往容器中写入数据
// resolve(0)
// 异步方式往容器中写入数据
setTimeout(() => {

resolve(1)
})
})

四:从Promise容器中读取数据(then方法负责)

1.then函数的定义

  • 哪里定义:容器内部定义
  • 函数职责:接收调用者传入的两个回调函数,根据容器存储的状态判断执行哪一个函数,同时把容器存储的数据作为输入执行这个函数。
  • 可选状态处理函数:允许调用者只针对某一个状态进行操作。
  • pending状态处理:pending状态时,容器没有拿到excutor运行结果的状态和数据,所以把两个回调函数分别放入回调函数数组onResolvedTodoList 和 onRejectedTodoList中,等待程序运行得到excutor执行结果的状态和数据并交付给容器管理之后再执行,也就是容器的resolve和reject方法调用之后。

容器内部对then函数的实现示例:

class Container {

constructor(excutor) {
}
resolve = value => {
}
reject = reason => {
}
onResolvedTodoList = [];
onRejectedTodoList = [];
then(onResolved, onRejected) {

onResolved = onResolved ? onResolved : value => value;
onRejected = onRejected ? onRejected : reason => {
 throw reason };
switch (this.state) {

case Container.PENDING:
this.onResolvedTodoList.push(() => {

try {

const value = onResolved(this.value);
resolve(value);
} catch (e) {

reject(e);
}
});
this.onRejectedTodoList.push(() => {

try {

const value = onRejected(this.reason);
reject(value);
} catch (e) {

reject(e);
}
});
break;
case Container.RESOLVED:
try {

const value = onResolved(this.value);
resolve(value);
} catch (e) {

reject(e);
}
break;
case Container.REJECTED:
try {

const value = onRejected(this.reason);
resolve(value);
} catch (e) {

reject(e);
}
break;
}
}
}

2.then函数的调用

  • 哪里调用:容器外部调用
  • 调用时机:容器实例构造之后。注意:在容器实例构造好之后,异步向容器中写入状态和数据之前的这个时间差内,容器内的状态和数据还没准备好。

容器外部对then函数的调用示例:

p1.then((value) => {

console.log(value)
}, (reason) => {

console.log(reason)
})

五:实现then函数的链式调用

  • then函数不支持链式调用的缺陷:then函数没有返回值,它虽然可以多次调用,但它只能根据容器状态和数据做一步操作,无法实现操作的流水线式处理,如此依然无法解决回调函数循环嵌套的问题。
  • then函数支持链式调用的优点:then函数根据回调函数的执行结果构造返回一个新的容器,这样外部就可以对then函数调用返回的容器状态和数据再做下一步操作。如此反复,就可以实现操作的流水线式处理解决回调函数嵌套问题

1.then函数的定义增强

then方法返回一个容器对象,这个新容器对象管理的状态和数据由回调函数的执行结果来决定。
  • 如果回调函数运行返回一个值,则调用resolve方法初始化这个新容器的状态和数据。
  • 如果回调函数运行返回一个容器并且不是新容器本身,则调用这个回调函数返回的容器的then方法来读取其内部管理的状态和数据,而后调用新容器的resolve或者reject方法来初始化新容器的状态和数据。这个先读取后写入的具体通信过程,是通过把新容器的resolve或者reject方法作为回调函数返回容器的下一步操作(也就是分别作为then方法的参数onResolved和onRejected)来实现的。
  • 如果回调函数运行返回新容器本身,如果不做特殊处理,则会造成读取新容器的状态和数据作为新容器的状态和数据的现象,永远都不会结束,陷入无限循环。这个问题的解决方案,就是让回调函数的运行(新容器的excutor)作为异步任务放入任务队列中,这样做的好处在初始化这个新容器的状态和数据之前,就可以拿到这个新容器对象进行判断

容器内部对then函数支持链式调用的实现示例:

class Container {

constructor(excutor) {
}
resolve = value => {
}
reject = reason => {
}
onResolvedTodoList = [];
onRejectedTodoList = [];
then(onResolved, onRejected) {

onResolved = onResolved ? onResolved : value => value;
onRejected = onRejected ? onRejected : reason => {
 throw reason };
let containerBack = new Container((resolve, reject) => {

switch (this.state) {

case Container.PENDING:
this.onResolvedTodoList.push(() => {

setTimeout(() => {

try {

const value = onResolved(this.value);
resolveContainer(containerBack, value, resolve, reject);
} catch (e) {

reject(e);
}
})
});
this.onRejectedTodoList.push(() => {

setTimeout(function () {

try {

const value = onRejected(this.reason);
resolveContainer(containerBack, value, resolve, reject);
} catch (e) {

reject(e);
}
})
});
break;
case Container.RESOLVED:
setTimeout(() => {

try {

const value = onResolved(this.value);
resolveContainer(containerBack, value, resolve, reject);
} catch (e) {

reject(e);
}
})
break;
case Container.REJECTED:
setTimeout(function () {

try {

const value = onRejected(this.reason);
resolveContainer(containerBack, value, resolve, reject);
} catch (e) {

reject(e);
}
})
break;
}
});
return containerBack
}
}
function resolveContainer(containerBack, value, resolve, reject) {

if (!(value instanceof Container)) {

resolve(value)
} else {

if (value === containerBack) {

reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
} else {

value.then(resolve, reject);
}
}
}

2.then函数的链式调用

  • then链式调用的目的:在调用then方法之后,调用者可以根据回调函数的执行结果再做下一步操作,也就是监听回调函数运行结果的回调函数

容器外部对then函数的链式调用示例:

const p2 = p1.then((value) => {

console.log(value)
}, (reason) => {

console.log(reason)
})
p2.then((value) => {

console.log('p2', value)
}, (reason) => {

console.log('p2', reason)
}).then((value) => {

console.log('p3', value)
}, (reason) => {

console.log('p3', reason)
})

本文结束,谢谢观看。
如若认可,一键三连。

版权声明
本文为[osc_um3gbrdm]所创,转载请带上原文链接,感谢
https://my.oschina.net/u/4267090/blog/4711074

  1. [front end -- JavaScript] knowledge point (IV) -- memory leakage in the project (I)
  2. This mechanism in JS
  3. Vue 3.0 source code learning 1 --- rendering process of components
  4. Learning the realization of canvas and simple drawing
  5. gin里获取http请求过来的参数
  6. vue3的新特性
  7. Get the parameters from HTTP request in gin
  8. New features of vue3
  9. vue-cli 引入腾讯地图(最新 api,rocketmq原理面试
  10. Vue 学习笔记(3,免费Java高级工程师学习资源
  11. Vue 学习笔记(2,Java编程视频教程
  12. Vue cli introduces Tencent maps (the latest API, rocketmq)
  13. Vue learning notes (3, free Java senior engineer learning resources)
  14. Vue learning notes (2, Java programming video tutorial)
  15. 【Vue】—props属性
  16. 【Vue】—创建组件
  17. [Vue] - props attribute
  18. [Vue] - create component
  19. 浅谈vue响应式原理及发布订阅模式和观察者模式
  20. On Vue responsive principle, publish subscribe mode and observer mode
  21. 浅谈vue响应式原理及发布订阅模式和观察者模式
  22. On Vue responsive principle, publish subscribe mode and observer mode
  23. Xiaobai can understand it. It only takes 4 steps to solve the problem of Vue keep alive cache component
  24. Publish, subscribe and observer of design patterns
  25. Summary of common content added in ES6 + (II)
  26. No.8 Vue element admin learning (III) vuex learning and login method analysis
  27. Write a mini webpack project construction tool
  28. Shopping cart (front-end static page preparation)
  29. Introduction to the fluent platform
  30. Webpack5 cache
  31. The difference between drop-down box select option and datalist
  32. CSS review (III)
  33. Node.js学习笔记【七】
  34. Node.js learning notes [VII]
  35. Vue Router根据后台数据加载不同的组件(思考-&gt;实现-&gt;不止于实现)
  36. Vue router loads different components according to background data (thinking - & gt; Implementation - & gt; (more than implementation)
  37. 【JQuery框架,Java编程教程视频下载
  38. [jQuery framework, Java programming tutorial video download
  39. Vue Router根据后台数据加载不同的组件(思考-&gt;实现-&gt;不止于实现)
  40. Vue router loads different components according to background data (thinking - & gt; Implementation - & gt; (more than implementation)
  41. 【Vue,阿里P8大佬亲自教你
  42. 【Vue基础知识总结 5,字节跳动算法工程师面试经验
  43. [Vue, Ali P8 teaches you personally
  44. [Vue basic knowledge summary 5. Interview experience of byte beating Algorithm Engineer
  45. 【问题记录】- 谷歌浏览器 Html生成PDF
  46. [problem record] - PDF generated by Google browser HTML
  47. 【问题记录】- 谷歌浏览器 Html生成PDF
  48. [problem record] - PDF generated by Google browser HTML
  49. 【JavaScript】查漏补缺 —数组中reduce()方法
  50. [JavaScript] leak checking and defect filling - reduce() method in array
  51. 【重识 HTML (3),350道Java面试真题分享
  52. 【重识 HTML (2),Java并发编程必会的多线程你竟然还不会
  53. 【重识 HTML (1),二本Java小菜鸟4面字节跳动被秒成渣渣
  54. [re recognize HTML (3) and share 350 real Java interview questions
  55. [re recognize HTML (2). Multithreading is a must for Java Concurrent Programming. How dare you not
  56. [re recognize HTML (1), two Java rookies' 4-sided bytes beat and become slag in seconds
  57. 【重识 HTML ,nginx面试题阿里
  58. 【重识 HTML (4),ELK原来这么简单
  59. [re recognize HTML, nginx interview questions]
  60. [re recognize HTML (4). Elk is so simple