Promise 原理解析与实现(遵循Promise/A+规范)

1.什么是Promise?

Promise是JS异步编程中的重要概念,异步抽象处理对象,是目前比较流行Javascript异步编程解决方案之一

2.对于几种常见异步编程方案

  • 回调函数
  • 事件监听
  • 发布/订阅
  • Promise对象

这里就拿回调函数说说

1.对于回调函数 我们用Jquery的ajax获取数据时 都是以回调函数方式获取的数据

$.get(url, (data) => {console.log(data)
)
复制代码

2.如果说 当我们需要发送多个异步请求 并且每个请求之间需要相互依赖 那这时 我们只能 以嵌套方式来解决 形成 "回调地狱"

$.get(url, data1 => {console.log(data1)$.get(data1.url, data2 => {console.log(data1)})
})
复制代码

这样一来,在处理越多的异步逻辑时,就需要越深的回调嵌套,这种编码模式的问题主要有以下几个:

  • 代码逻辑书写顺序与执行顺序不一致,不利于阅读与维护。
  • 异步操作的顺序变更时,需要大规模的代码重构。
  • 回调函数基本都是匿名函数,bug 追踪困难。
  • 回调函数是被第三方库代码(如上例中的 ajax )而非自己的业务代码所调用的,造成了 IoC 控制反转。

Promise 处理多个相互关联的异步请求

1.而我们Promise 可以更直观的方式 来解决 "回调地狱"

const request = url => { return new Promise((resolve, reject) => {$.get(url, data => {resolve(data)});})
};// 请求data1
request(url).then(data1 => {return request(data1.url);   
}).then(data2 => {return request(data2.url);
}).then(data3 => {console.log(data3);
}).catch(err => throw new Error(err));
复制代码

2.相信大家在 vue/react 都是用axios fetch 请求数据 也都支持 Promise API

import axios from 'axios';
axios.get(url).then(data => {console.log(data)
})
复制代码

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。

3.Promise使用

1.Promise 是一个构造函数, new Promise 返回一个 promise对象 接收一个excutor执行函数作为参数, excutor有两个函数类型形参resolve reject

const promise = new Promise((resolve, reject) => {// 异步处理// 处理结束后、调用resolve 或 reject
});复制代码

2.promise相当于一个状态机

promise的三种状态

  • pending
  • fulfilled
  • rejected

1.promise 对象初始化状态为 pending 2.当调用resolve(成功),会由pending => fulfilled 3.当调用reject(失败),会由pending => rejected

注意promsie状态 只能由 pending => fulfilled/rejected, 一旦修改就不能再变

3.promise对象方法

1.then方法注册 当resolve(成功)/reject(失败)的回调函数

// onFulfilled 是用来接收promise成功的值
// onRejected 是用来接收promise失败的原因
promise.then(onFulfilled, onRejected);
复制代码

then方法是异步执行的

2.resolve(成功) onFulfilled会被调用

const promise = new Promise((resolve, reject) => {resolve('fulfilled'); // 状态由 pending => fulfilled
});
promise.then(result => { // onFulfilledconsole.log(result); // 'fulfilled' 
}, reason => { // onRejected 不会被调用})
复制代码

3.reject(失败) onRejected会被调用

const promise = new Promise((resolve, reject) => {reject('rejected'); // 状态由 pending => rejected
});
promise.then(result => { // onFulfilled 不会被调用}, reason => { // onRejected console.log(reason); // 'rejected'
})
复制代码

4.promise.catch

在链式写法中可以捕获前面then中发送的异常,

promise.catch(onRejected)
相当于
promise.then(null, onRrejected);// 注意
// onRejected 不能捕获当前onFulfilled中的异常
promise.then(onFulfilled, onRrejected); // 可以写成:
promise.then(onFulfilled).catch(onRrejected);   
复制代码

4.promise chain

promise.then方法每次调用 都返回一个新的promise对象 所以可以链式写法

function taskA() {console.log("Task A");
}
function taskB() {console.log("Task B");
}
function onRejected(error) {console.log("Catch Error: A or B", error);
}var promise = Promise.resolve();
promise.then(taskA).then(taskB).catch(onRejected) // 捕获前面then方法中的异常
复制代码

5.Promise的静态方法

1.Promise.resolve 返回一个fulfilled状态的promise对象

Promise.resolve('hello').then(function(value){console.log(value);
});Promise.resolve('hello');
// 相当于
const promise = new Promise(resolve => {resolve('hello');
});
复制代码

2.Promise.reject 返回一个rejected状态的promise对象

Promise.reject(24);
new Promise((resolve, reject) => {reject(24);
});
复制代码

3.Promise.all 接收一个promise对象数组为参数

只有全部为resolve才会调用 通常会用来处理 多个并行异步操作

const p1 = new Promise((resolve, reject) => {resolve(1);
});const p2 = new Promise((resolve, reject) => {resolve(2);
});const p3 = new Promise((resolve, reject) => {reject(3);
});Promise.all([p1, p2, p3]).then(data => { console.log(data); // [1, 2, 3] 结果顺序和promise实例数组顺序是一致的
}, err => {console.log(err);
});
复制代码

4.Promise.race 接收一个promise对象数组为参数

Promise.race 只要有一个promise对象进入 FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理。

function timerPromisefy(delay) {return new Promise(function (resolve, reject) {setTimeout(function () {resolve(delay);}, delay);});
}
var startDate = Date.now();Promise.race([timerPromisefy(10),timerPromisefy(20),timerPromisefy(30)
]).then(function (values) {console.log(values); // 10
});
复制代码

5.Promise的finally

Promise.prototype.finally = function (callback) {let P = this.constructor;return this.then(value  => P.resolve(callback()).then(() => value),reason => P.resolve(callback()).then(() => { throw reason }));
};
复制代码

4. Promise 代码实现

/*** Promise 实现 遵循promise/A+规范* Promise/A+规范译文:* https://malcolmyu.github.io/2015/06/12/Promises-A-Plus/#note-4*/// promise 三个状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";function Promise(excutor) {let that = this; // 缓存当前promise实例对象that.status = PENDING; // 初始状态that.value = undefined; // fulfilled状态时 返回的信息that.reason = undefined; // rejected状态时 拒绝的原因that.onFulfilledCallbacks = []; // 存储fulfilled状态对应的onFulfilled函数that.onRejectedCallbacks = []; // 存储rejected状态对应的onRejected函数function resolve(value) { // value成功态时接收的终值if(value instanceof Promise) {return value.then(resolve, reject);}// 为什么resolve 加setTimeout?// 2.2.4规范 onFulfilled 和 onRejected 只允许在 execution context 栈仅包含平台代码时运行.// 注1 这里的平台代码指的是引擎、环境以及 promise 的实施代码。实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。setTimeout(() => {// 调用resolve 回调对应onFulfilled函数if (that.status === PENDING) {// 只能由pending状态 => fulfilled状态 (避免调用多次resolve reject)that.status = FULFILLED;that.value = value;that.onFulfilledCallbacks.forEach(cb => cb(that.value));}});}function reject(reason) { // reason失败态时接收的拒因setTimeout(() => {// 调用reject 回调对应onRejected函数if (that.status === PENDING) {// 只能由pending状态 => rejected状态 (避免调用多次resolve reject)that.status = REJECTED;that.reason = reason;that.onRejectedCallbacks.forEach(cb => cb(that.reason));}});}// 捕获在excutor执行器中抛出的异常// new Promise((resolve, reject) => {//     throw new Error('error in excutor')// })try {excutor(resolve, reject);} catch (e) {reject(e);}
}/*** resolve中的值几种情况:* 1.普通值* 2.promise对象* 3.thenable对象/函数*//*** 对resolve 进行改造增强 针对resolve中不同值情况 进行处理* @param  {promise} promise2 promise1.then方法返回的新的promise对象* @param  {[type]} x         promise1中onFulfilled的返回值* @param  {[type]} resolve   promise2的resolve方法* @param  {[type]} reject    promise2的reject方法*/
function resolvePromise(promise2, x, resolve, reject) {if (promise2 === x) {  // 如果从onFulfilled中返回的x 就是promise2 就会导致循环引用报错return reject(new TypeError('循环引用'));}let called = false; // 避免多次调用// 如果x是一个promise对象 (该判断和下面 判断是不是thenable对象重复 所以可有可无)if (x instanceof Promise) { // 获得它的终值 继续resolveif (x.status === PENDING) { // 如果为等待态需等待直至 x 被执行或拒绝 并解析y值x.then(y => {resolvePromise(promise2, y, resolve, reject);}, reason => {reject(reason);});} else { // 如果 x 已经处于执行态/拒绝态(值已经被解析为普通值),用相同的值执行传递下去 promisex.then(resolve, reject);}// 如果 x 为对象或者函数} else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {try { // 是否是thenable对象(具有then方法的对象/函数)let then = x.then;if (typeof then === 'function') {then.call(x, y => {if(called) return;called = true;resolvePromise(promise2, y, resolve, reject);}, reason => {if(called) return;called = true;reject(reason);})} else { // 说明是一个普通对象/函数resolve(x);}} catch(e) {if(called) return;called = true;reject(e);}} else {resolve(x);}
}/*** [注册fulfilled状态/rejected状态对应的回调函数]* @param  {function} onFulfilled fulfilled状态时 执行的函数* @param  {function} onRejected  rejected状态时 执行的函数* @return {function} newPromsie  返回一个新的promise对象*/
Promise.prototype.then = function(onFulfilled, onRejected) {const that = this;let newPromise;// 处理参数默认值 保证参数后续能够继续执行onFulfilled =typeof onFulfilled === "function" ? onFulfilled : value => value;onRejected =typeof onRejected === "function" ? onRejected : reason => {throw reason;};// then里面的FULFILLED/REJECTED状态时 为什么要加setTimeout ?// 原因:// 其一 2.2.4规范 要确保 onFulfilled 和 onRejected 方法异步执行(且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行) 所以要在resolve里加上setTimeout// 其二 2.2.6规范 对于一个promise,它的then方法可以调用多次.(当在其他程序中多次调用同一个promise的then时 由于之前状态已经为FULFILLED/REJECTED状态,则会走的下面逻辑),所以要确保为FULFILLED/REJECTED状态后 也要异步执行onFulfilled/onRejected// 其二 2.2.6规范 也是resolve函数里加setTimeout的原因// 总之都是 让then方法异步执行 也就是确保onFulfilled/onRejected异步执行// 如下面这种情景 多次调用p1.then// p1.then((value) => { // 此时p1.status 由pedding状态 => fulfilled状态//     console.log(value); // resolve//     // console.log(p1.status); // fulfilled//     p1.then(value => { // 再次p1.then 这时已经为fulfilled状态 走的是fulfilled状态判断里的逻辑 所以我们也要确保判断里面onFuilled异步执行//         console.log(value); // 'resolve'//     });//     console.log('当前执行栈中同步代码');// })// console.log('全局执行栈中同步代码');//if (that.status === FULFILLED) { // 成功态return newPromise = new Promise((resolve, reject) => {setTimeout(() => {try{let x = onFulfilled(that.value);resolvePromise(newPromise, x, resolve, reject); // 新的promise resolve 上一个onFulfilled的返回值} catch(e) {reject(e); // 捕获前面onFulfilled中抛出的异常 then(onFulfilled, onRejected);}});})}if (that.status === REJECTED) { // 失败态return newPromise = new Promise((resolve, reject) => {setTimeout(() => {try {let x = onRejected(that.reason);resolvePromise(newPromise, x, resolve, reject);} catch(e) {reject(e);}});});}if (that.status === PENDING) { // 等待态// 当异步调用resolve/rejected时 将onFulfilled/onRejected收集暂存到集合中return newPromise = new Promise((resolve, reject) => {that.onFulfilledCallbacks.push((value) => {try {let x = onFulfilled(value);resolvePromise(newPromise, x, resolve, reject);} catch(e) {reject(e);}});that.onRejectedCallbacks.push((reason) => {try {let x = onRejected(reason);resolvePromise(newPromise, x, resolve, reject);} catch(e) {reject(e);}});});}
};/*** Promise.all Promise进行并行处理* 参数: promise对象组成的数组作为参数* 返回值: 返回一个Promise实例* 当这个数组里的所有promise对象全部变为resolve状态的时候,才会resolve。*/
Promise.all = function(promises) {return new Promise((resolve, reject) => {let done = gen(promises.length, resolve);promises.forEach((promise, index) => {promise.then((value) => {done(index, value)}, reject)})})
}function gen(length, resolve) {let count = 0;let values = [];return function(i, value) {values[i] = value;if (++count === length) {console.log(values);resolve(values);}}
}/*** Promise.race* 参数: 接收 promise对象组成的数组作为参数* 返回值: 返回一个Promise实例* 只要有一个promise对象进入 FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理(取决于哪一个更快)*/
Promise.race = function(promises) {return new Promise((resolve, reject) => {promises.forEach((promise, index) => {promise.then(resolve, reject);});});
}// 用于promise方法链时 捕获前面onFulfilled/onRejected抛出的异常
Promise.prototype.catch = function(onRejected) {return this.then(null, onRejected);
}Promise.resolve = function (value) {return new Promise(resolve => {resolve(value);});
}Promise.reject = function (reason) {return new Promise((resolve, reject) => {reject(reason);});
}/*** 基于Promise实现Deferred的* Deferred和Promise的关系* - Deferred 拥有 Promise* - Deferred 具备对 Promise的状态进行操作的特权方法(resolve reject)**参考jQuery.Deferred*url: http://api.jquery.com/category/deferred-object/*/
Promise.deferred = function() { // 延迟对象let defer = {};defer.promise = new Promise((resolve, reject) => {defer.resolve = resolve;defer.reject = reject;});return defer;
}/*** Promise/A+规范测试* npm i -g promises-aplus-tests* promises-aplus-tests Promise.js*/try {module.exports = Promise
} catch (e) {
}复制代码

Promise测试

npm i -g promises-aplus-tests
promises-aplus-tests Promise.js
复制代码

如何主动终止Promise调用链

const p1 = new Promise((resolve, reject) => {setTimeout(() => { // 异步操作resolve('start')}, 1000);
});p1.then((result) => {console.log('a', result); return Promise.reject('中断后续调用'); // 此时rejected的状态将直接跳到catch里,剩下的调用不会再继续
}).then(result => {console.log('b', result);
}).then(result => {console.log('c', result);
}).catch(err => {console.log(err);
});// a start
// 中断后续调用
复制代码

相关知识参考资料

  • ES6-promise
  • Promises/A+规范-英文
  • Promises/A+规范-翻译1
  • Promises/A+规范-翻译-推荐
  • JS执行栈
  • Javascript异步编程的4种方法

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/391441.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

php 数据访问练习:投票页面

<!--投票界面--> <html> <head> <title></title> <meta charset"UTF-8"/> <link rel"stylesheet" type"text/css" href"bootstrap.min.css"/> <script src"bootstrap.min.js"…

深入理解InnoDB(3)—索引的存储结构

1. 索引的各种存储结构及其优缺点 1.1 二叉树 优点&#xff1a; 二叉树是一种比顺序结构更加高效地查找目标元素的结构&#xff0c;它可以从第一个父节点开始跟目标元素值比较&#xff0c;如果相等则返回当前节点&#xff0c;如果目标元素值小于当前节点&#xff0c;则移动到左…

有抱负/初级开发人员的良好习惯-避免使用的习惯

When youre learning to code, it can be easy to pick up some nasty habits along the way. Here are some tips to avoid common bad habits, and the good habits to keep in mind.当您学习编码时&#xff0c;很容易在此过程中养成一些讨厌的习惯。 这里有一些技巧&#xf…

业精于勤荒于嬉---Go的GORM查询

查询 //通过主键查询第一条记录 db.First(&user)SELECT * FROM users ORDER BY id LIMIT 1;// 随机取一条记录 db.Take(&user)SELECT * FROM users LIMIT 1;// 通过主键查询最后一条记录 db.Last(&user)SELECT * FROM users ORDER BY id DESC LIMIT 1;// 拿到所有的…

apache 虚拟主机详细配置:http.conf配置详解

Apache的配置文件http.conf参数含义详解 Apache的配置由httpd.conf文件配置&#xff0c;因此下面的配置指令都是在httpd.conf文件中修改。主站点的配置(基本配置) (1) 基本配置:ServerRoot "/mnt/software/apache2" #你的apache软件安装的位置。其它指定的目录如果没…

深入理解InnoDB(4)—索引使用

1. 索引的代价 在了解索引的代价之前&#xff0c;需要再次回顾一下索引的数据结构B树 如上图&#xff0c;是一颗b树&#xff0c;关于b树的定义可以参见B树&#xff0c;这里只说一些重点&#xff0c;浅蓝色的块我们称之为一个磁盘块&#xff0c;可以看到每个磁盘块包含几个数据…

[BZOJ1626][Usaco2007 Dec]Building Roads 修建道路

1626: [Usaco2007 Dec]Building Roads 修建道路 Time Limit: 5 Sec Memory Limit: 64 MB Submit: 1730 Solved: 727 [Submit][Status][Discuss]Description Farmer John最近得到了一些新的农场&#xff0c;他想新修一些道路使得他的所有农场可以经过原有的或是新修的道路互达…

双城记s001_双城记! (使用数据讲故事)

双城记s001Keywords: Data science, Machine learning, Python, Web scraping, Foursquare关键字&#xff1a;数据科学&#xff0c;机器学习&#xff0c;Python&#xff0c;Web抓取&#xff0c;Foursquare https://br.pinterest.com/pin/92816442292506979/https://br.pintere…

python:linux中升级python版本

https://www.cnblogs.com/gne-hwz/p/8586430.html 转载于:https://www.cnblogs.com/gcgc/p/11446403.html

web前端面试总结

2019独角兽企业重金招聘Python工程师标准>>> 摘要&#xff1a;前端的东西特别多&#xff0c;面试的时候我们如何从容应对&#xff0c;作为一个老兵&#xff0c;我在这里分享几点我的经验。 一、javascript 基础(es5) 1、原型&#xff1a;这里可以谈很多&#xff0c;…

783. 二叉搜索树节点最小距离(dfs)

给你一个二叉搜索树的根节点 root &#xff0c;返回 树中任意两不同节点值之间的最小差值 。 注意&#xff1a;本题与 530&#xff1a;https://leetcode-cn.com/problems/minimum-absolute-difference-in-bst/ 相同 示例 1&#xff1a; 输入&#xff1a;root [4,2,6,1,3] 输…

linux epoll机制对TCP 客户端和服务端的监听C代码通用框架实现

1 TCP简介 tcp是一种基于流的应用层协议&#xff0c;其“可靠的数据传输”实现的原理就是&#xff0c;“拥塞控制”的滑动窗口机制&#xff0c;该机制包含的算法主要有“慢启动”&#xff0c;“拥塞避免”&#xff0c;“快速重传”。 2 TCP socket建立和epoll监听实现 数据结构…

linux中安装robot环境

https://www.cnblogs.com/lgqboke/p/8252488.html&#xff08;文中验证robotframework命令应该为 robot --version&#xff09; 可能遇到的问题&#xff1a; 1、python版本太低 解决&#xff1a;升级python https://www.cnblogs.com/huaxingtianxia/p/7986734.html 2、pip安装报…

angular 模块构建_我如何在Angular 4和Magento上构建人力资源门户

angular 模块构建Sometimes trying a new technology mashup works wonders. Both Magento 2 Angular 4 are very commonly talked about, and many consider them to be the future of the development industry. 有时尝试新技术的mashup会产生奇迹。 Magento 2 Angular 4都…

tableau破解方法_使用Tableau浏览Netflix内容的简单方法

tableau破解方法Are you struggling to perform EDA with R and Python?? Here is an easy way to do exploratory data analysis using Tableau.您是否正在努力使用R和Python执行EDA&#xff1f; 这是使用Tableau进行探索性数据分析的简单方法。 Lets Dive in to know the …

六周第三次课

2019独角兽企业重金招聘Python工程师标准>>> 六周第三次课 9.6/9.7 awk awk也是流式编辑器&#xff0c;针对文档中的行来操作&#xff0c;一行一行地执行。 awk比sed更强大的功能是它支持了分段。 -F选项的作用是指定分隔符&#xff0c;如果不加-F选项&#xff0c;…

面试题字符集和编码区别_您和理想工作之间的一件事-编码面试!

面试题字符集和编码区别A recruiter calls you for a position with your dream company. You get extremely excited and ask about their recruiting process. He replies saying “Its nothing big, you will have 5 coding rounds with our senior tech team, just the sta…

初探Golang(1)-变量

要学习golang&#xff0c;当然要先配置好相关环境啦。 1. Go 安装包下载 https://studygolang.com/dl 在Windows下&#xff0c;直接下载msi文件&#xff0c;在安装界面选择安装路径&#xff0c;然后一直下一步就行了。 在cmd下输入 go version即可看到go安装成功 2. Golan…

macaca web(4)

米西米西滴&#xff0c;吃过中午饭来一篇&#xff0c;话说&#xff0c;上回书说道macaca 测试web&#xff08;3&#xff09;&#xff0c;参数驱动来搞&#xff0c;那么有小伙本又来给雷子来需求&#xff0c; 登录模块能不能给我给重新封装一下吗&#xff0c; 我说干嘛封装&…

linux中安装cx_Oracle

https://blog.csdn.net/w657395940/article/details/41144225 各种尝试都&#xff0c;最后 pip install cx-Oracle 成功导入 转载于:https://www.cnblogs.com/gcgc/p/11447583.html