如何实现一个符合规范的Promise
异步发展历程
我们知道Promise的出现是为了处理js中的异步操作。在Promise出现之前,多用回调来处理异步操作。一旦异步操作稍微复杂起来,就很容易出现传说中的回调地狱,会导致代码的可读性和可维护性降低。 例如我们要实现以下异步操作,用回调的执行方式如下:
let fs = require('fs');
fs.readFile('a.txt', (err,data) => {fs.readFile('b.txt', (err,data) => {fs.readFile('c.txt', (err,data) => {//...});});
});
复制代码
而用Promise则可以这样写:
let util = require('util');
let fs = require('fs');
let readFile = util.promisify(fs.readFile);readFile('a.txt').then(data => {return readFile('b.txt');}).then(data => {return readFile('c.txt');}).then(data => {//...});
复制代码
当然我们也可以用ES7中的async/await去写,会更简便:
let util = require('util');
let fs = require('fs');
let readFile = util.promisify(fs.readFile);
async function read() {await readFile('a.txt');await readFile('b.txt');await readFile('c.txt');// ...
}
read()
复制代码
通过对比,可以发现async/await的写法是最简洁的,基本上趋近于同步代码的写法了。不过async/await也是在promise的基础上演化的,可以看作是基于promise的语法糖。所以接下来我们就来看看如何手写一个promise.
Promise代码基本用法
let p = new Promise((resolve, reject) => {resolve(123)
})
p.then((data) => {console.log(data) // 此处可以取到resolve中的传入的参数值
}, (err) => {console.log('e', err)
})
复制代码
通过new Promise()传入一个executor参数
class Promise {constructor(executor) {this.status = 'pending' // 默认状态是等待态this.value = undefinedthis.reason = undefinedlet resolve = value => {if (this.status === 'pending') {this.value = valuethis.status = 'resolved'}}let reject = reason => {if (this.status === 'pending') {this.reason = reasonthis.status = 'rejecte'}}try {executor(resolve, reject)} catch (e) {reject(e)}}// 会取promise的返回结果作为外层下一次then的参数// 如果返回// then方法调用后,返回的是一个新的promisethen(onFufilled, onRjected) {if (this.status === 'resolved') {onFufilled(this.value)}if (this.status === 'rejected') {onRjected(this.reason)}}
}
复制代码
以上就实现了具有最基本功能的promise啦,但这还远远不够哦,因为这时还不支持在promise中执行异步操作。如:
let p = new Promise((resolve, reject) => {// promise中的异步操作setTimeout(() => {resolve(123)
}, 1000)
})
p.then((data) => {console.log(data) // 此时取不到resolve中的参数值
}, (err) => {console.log('e', err)
})
复制代码
所以当promise是pending态时,我们可以在then方法调用时先将onFulfilled和onRjected保存起来,当状态转换为resolved态或rejected时调用。这里用到了发布订阅的思想。
constructor(executor) {// ...this.onResolvedCallbacks = [] // 存放成功的回调 发布订阅this.onRejectedCallbacks = [] // 存放失败的回调let resolve = value => {if (this.status === 'pending') {// ...this.onResolvedCallbacks.forEach(fn => fn()) // 发布}}let reject = reason => {if (this.status === 'pending') {// ...this.onRejectedCallbacks.forEach(fn => fn())}}// .
}
then(onFufilled, onRjected) {// ...if (this.status === 'pending') {this.onResolvedCallbacks.push(() => { // 订阅onFufilled(this.value)}) this.onRejectedCallbacks.push(() => { // 订阅onRjected(this.reason)}) }}
}
复制代码
至此我们的promise已经支持异步代码了。接下来我们需要实现then方法的链式调用,这里是比较难和绕的部分。then方法有如下特点:
- then方法每次都会返回一个新的promise,如若显式返回一个promise,就将此promise作为其返回值(不能是当前调用then方法的promise),如若返回一个普通值,则需手动生成一个新的promise并将其return
- then方法是异步调用的
- 若then方法中出现异常,则调用失败态方法(reject)跳转到下一个then的onRejected
- 当then()中未传回调参数时可以看作跳过当前then方法继续向下个then方法执行,即值的穿透
function resolvePromise(promise2, x, resolve, reject) {// 判断x是不是promiseif (promise2 === x) { // x为promise2,不能自己等待自己完成return reject(new TypeError('循环引用'))}if (x !== null && (typeof x === 'object' || typeof x === 'function')) { // x是个promiselet called // 防止resolve后调用rejecttry {let then = x.then // 取x的then方法if (typeof then === 'function') { // 如果then是函数就认为是promisethen.call(x, y => { // 如果y是promise就继续递归解析promiseif (called) returncalled = trueresolvePromise(promise2, y, resolve, reject)}, err => {if (called) returncalled = truereject(err)})}} catch(e) {if (called) returncalled = truereject(e)}} else { // then是一个普通对象resolve(x)}
}then(onFufilled, onRjected) {// 解决不传onFufilled onRjected的问题 , 值的穿透onFufilled = typeof onFufilled === 'function' ? onFufilled : y => yonRjected = typeof onRjected === 'function' ? onRjected : err => { throw err }let promise2 // then需要返回一个新的promiseif (this.status === 'resolved') {promise2 = new Promise((resolve, reject) => {// x为promise则取其作为promise2的成功结果, 如果x为一个普通值,则作为promise2成功的结果console.log(promise2)setTimeout(() => {try {let x = onFufilled(this.value)resolvePromise(promise2, x, resolve, reject)} catch(e) {reject(e)}}, 0)})//console.log(promise2)// resolvePromise(promise2, x, resolve, reject)}if (this.status === 'rejected') {promise2 = new Promise((resolve, reject) => {setTimeout(() => {try {let x = onRjected(this.reason)resolvePromise(promise2, x, resolve, reject)} catch(e) {reject(e)}}, 0)})}if (this.status === 'pending') { // 主要解决executor里异步代码promise2 = new Promise((resolve, reject) => {this.onResolvedCallbacks.push(() => { // 订阅setTimeout(() => {try {let x = onFufilled(this.value)resolvePromise(promise2, x, resolve, reject)} catch(e) {reject(e)}}, 0)})this.onRejectedCallbacks.push(() => {setTimeout(() => {try {let x = onFufilled(this.reason)resolvePromise(promise2, x, resolve, reject)} catch (e) {reject(e)}}, 0)})})}return promise2}复制代码
resolvePromise
用来解析then方法的返回值
p.then(data => {return new Promise((resolve,reject)=>{//resolve传入的还是Promiseresolve(new Promise((resolve,reject)=>{resolve(2);}));});
})
复制代码
如何实现一个符合规范的Promise相关推荐
- 实现一个符合标准的Promise
-- What i can't create, i don't understant 前言 实现Promise的目的是为了深入的理解Promies,以在项目中游刃有余的使用它.完整的代码见gitHub ...
- 实现一个完美符合Promise/A+规范的Promise
原文在我的博客中:原文地址 如果文章对您有帮助,您的star是对我最好的鼓励- 简要介绍:Promise允许我们通过链式调用的方式来解决"回调地狱"的问题,特别是在异步过程中,通过 ...
- 实现一个符合 Promise/A+ 规范的 MyPromise
Promise 实现一个符合 Promise/A+ 规范的 MyPromise,并实现 resolve.reject.all.race.defer.deferred等静态方法. MyPromise 作 ...
- 方法 手写promise_实现一个符合 Promise/A+规范的 Promise(typescript 版)
(给前端大全加星标,提升前端技能) 转自:Col0ring juejin.cn/post/6886360224308035598 写在前面 没错,这又是一篇关于手写 Promise 的文章,想必大家已 ...
- 深入理解Promise并写一个符合Promise a+规范的Promise代码
深入理解Promise并写一个符合Promise a+规范的Promise代码 关于Promise函数可以参考我写的这篇文章https://www.cnblogs.com/qiaohong/p/770 ...
- 写一个符合 Promises/A+ 规范并可配合 ES7 async/await 使用的 Promise
原文地址 从历史的进程来看,Javascript 异步操作的基本单位已经从 callback 转换到 Promise.除了在特殊场景使用 stream,RxJs 等更加抽象和高级的异步数据获取和操作流 ...
- 一步步写一个符合Promise/A+规范的库
Promise本意是承诺,在程序中的意思就是承诺我过一段时间后会给你一个结果. ES6 中采用了 Promise/A+ 规范,Promise 实现之前,当然要先了解 Promise/A+ 规范,规范地 ...
- 一步步写一个符合Promise/A+规范的库 1
Promise本意是承诺,在程序中的意思就是承诺我过一段时间后会给你一个结果. ES6 中采用了 Promise/A+ 规范,Promise 实现之前,当然要先了解 Promise/A+ 规范,规范地 ...
- 手写一款符合Promise/A+规范的Promise
手写一款符合Promise/A+规范的Promise 长篇预警!有点长,可以选择性观看.如果对Promise源码不是很清楚,还是推荐从头看,相信你认真从头看到尾,并且去实际操作了,肯定会有收获的.主要 ...
最新文章
- React React-Redux的安装,使用
- Sync 攻击原理及防范技术
- Entity Framework 学习高级篇2—改善EF代码的方法(下)
- ML:MLOps系列讲解之《端到端 ML工作流生命周期》解读
- SEO【总结】by 2019年5月
- 软件项目组织管理(四)项目集成管理
- 讯飞智能录音笔SR101:考研的温暖陪伴
- mysql char(36)_MySQL中char(36)被认为是GUID导致的BUG及解决方案
- PHP向右侧拉菜单,测试使用中
- T SQL + 正则表达式
- python学习------面向对象进阶
- autocoder自动代码生成器_Spring Boot 集成MyBatis Plus代码生成器
- 苹果游戏开发教程之如何使用 SpriteKit 和 GameplayKit 制作你的街机手机游戏
- innerHTML, innerText, outerHTML, outerText的区别
- Vue开发实例(04)之更换项目入口
- postgresql 数据库迁移
- 遥感影像镶嵌及实现(四)
- Excel VBA高级编程-微信群发消息工具
- 从fasta文件中筛选序列并输出
- 【LINUX】系统安全
热门文章
- 抗滑桩初始弹性系数计算_抗滑桩配筋计算.docx
- 全栈python_Pyodide:在浏览器端实现Python全栈科学计算
- Java9 jar兼容_java9新特性-6-多版本兼容jar包
- android开发自定义选择器,创建自定义android选择器
- 老式的计算机比现在的老太多了英语,老人自学英语30年成老师
- angular 关闭当前页_angular刷新当前页 angularjs页面不刷新的解决办法 - 电脑常识 - 服务器之家...
- mysql三高讲解(二):2.2 B+树的B的意义
- html选择第一个clss,css3如何选择第一个子元素?
- 我的世界服务器怎么注册密码1.9,我的世界1.9.x /1.10服务器BEST CRAFT(最佳工艺服务器)...
- mysql 8 nosql_MySQL8.0-NoSQL和SQL的对比及MySQL的优势