用es5实现es6的promise,彻底搞懂promise的原理
1.promise的含义
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise
对象。
所谓Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
Promise
对象有以下两个特点。
(1)对象的状态不受外界影响。Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise
这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise
对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
有了Promise
对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise
对象提供统一的接口,使得控制异步操作更加容易。
Promise
也有一些缺点。首先,无法取消Promise
,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise
内部抛出的错误,不会反应到外部。第三,当处于pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
2.小试promise用法:
Promise
构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
resolve
函数的作用是,将Promise
对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject
函数的作用是,将Promise
对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
var p=new Promise(function(resolve,rejcet){setTimeout(function(){if(true){//异步操作成功resolve('success');}else{//异步操作失败rejcet('failure');}},1000);
});
p.then(function(value){//成功的回调console.log(value);
},function(error){//失败的回调console.log(error);
});复制代码
从demo中可以看到,在ES6中Promise
对象是一个构造函数,用来生成Promise
实例。并且Promise
实例生成以后,可以用then
方法分别指定resolved
状态和rejected
状态的回调函数。
3.手写一个promise,彻底掌握它的原理
首先,我们先搭建好代码的骨架:
function Promise(callback) {var self = this //promise实例self.status = 'PENDING' // Promise当前的状态self.data = undefined // Promise的值self.onResolvedCallback = [] // Promise resolve时的回调函数集self.onRejectedCallback = [] // Promise reject时的回调函数集callback(resolve, reject) // 执行executor并传入相应的参数function resolve(value){}function reject(error){}
}
// 添加我们的then方法
Promise.prototype.then=function(){}复制代码
我们先创建一个Promise构造函数,并传入一个回调函数callback,callback里面传入两个函数作为参数,一个是resove,一个是reject。并在Promise的原型上加入我们的then方法。框架搭好了,接下来我们来一点点的完善框架里面的内容,可以这么说,把resolve,reject和then补充完,基本可以说就是把Promise完成了。
然后紧接着,我们完善一下resolve和reject:
function Promise(callback) {var self = thisself.status = 'PENDING' // Promise当前的状态self.data = undefined // Promise的值self.onResolvedCallback = [] // Promise resolve时的回调函数集self.onRejectedCallback = [] // Promise reject时的回调函数集callback(resolve, reject) // 执行executor并传入相应的参数function resolve(value){if(self.status=='PENDING'){self.status=='FULFILLED';self.data=value;// 依次执行成功之后的函数栈for(var i = 0; i < self.onResolvedCallback.length; i++) {self.onResolvedCallback[i](value)}}}function rejecte(error){if (self.status === 'PENDING') {self.status = 'REJECTED'self.data = error;// 依次执行失败之后的函数栈for(var i = 0; i < self.onRejectedCallback.length; i++) {self.onRejectedCallback[i](error)}}}
}复制代码
接下来我们实现我们的then方法:
then方法是Promise的核心,因此这里会花比较大的篇幅去介绍then:
一个promise的then接受两个参数:
promise.then(onFulfilled, onRejected)复制代码
onFulfilled
和 onRejected
都是可选参数。
- 如果
onFulfilled
不是函数,其必须被忽略 - 如果
onRejected
不是函数,其必须被忽略
onFulfilled 特性
如果 onFulfilled
是函数:
- 当
promise
执行结束后其必须被调用,其第一个参数为promise
的终值,也就是resolve传过来的值 - 在
promise
执行结束前其不可被调用 - 其调用次数不可超过一次
onRejected 特性
如果 onRejected
是函数:
- 当
promise
被拒绝执行后其必须被调用,其第一个参数为promise
的据因,也就是reject传过来的值 - 在
promise
被拒绝执行前其不可被调用 - 其调用次数不可超过一次
调用时机
onFulfilled
和 onRejected
只有在执行环境堆栈仅包含平台代码时才可被调用(平台代码指引擎、环境以及 promise 的实施代码)
调用要求
onFulfilled
和 onRejected
必须被作为函数调用(即没有 this
值,在严格模式(strict) 中,函数 this
的值为 undefined
;在非严格模式中其为全局对象。)
多次调用
then
方法可以被同一个 promise
调用多次
- 当
promise
成功执行时,所有onFulfilled
需按照其注册顺序依次回调 - 当
promise
被拒绝执行时,所有的onRejected
需按照其注册顺序依次回调
返回
then
方法必须返回一个 promise
对象
promise2 = promise1.then(onFulfilled, onRejected);复制代码
- 如果
onFulfilled
或者onRejected
返回一个值x
,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)
- 如果
onFulfilled
或者onRejected
抛出一个异常e
,则promise2
必须拒绝执行,并返回拒因e
- 如果
onFulfilled
不是函数且promise1
成功执行,promise2
必须成功执行并返回相同的值 - 如果
onRejected
不是函数且promise1
拒绝执行,promise2
必须拒绝执行并返回相同的据因
不论 promise1
被 reject 还是被 resolve 时 promise2
都会被 resolve,只有出现异常时才会被 rejected。
每个Promise对象都可以在其上多次调用then方法,而每次调用then返回的Promise的状态取决于那一次调用then时传入参数的返回值,所以then不能返回this,因为then每次返回的Promise的结果都有可能不同。
接下来我们来写我们的then方法:
Promise.prototype.then = function(onResolved, onRejected) {var self = thisvar promise2// 根据标准,如果then的参数不是function,则我们需要忽略它,此处以如下方式处理onResolved = typeof onResolved === 'function' ? onResolved : function(value) {}onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {}if (self.status === 'resolved') {// 如果promise1(此处即为this/self)的状态已经确定并且是resolved,我们调用onResolved// 因为考虑到有可能throw,所以我们将其包在try/catch块里return promise2 = new Promise(function(resolve, reject) {try {var x = onResolved(self.data)if (x instanceof Promise) { // 如果onResolved的返回值是一个Promise对象,直接取它的结果做为promise2的结果x.then(resolve, reject)}resolve(x) // 否则,以它的返回值做为promise2的结果} catch (e) {reject(e) // 如果出错,以捕获到的错误做为promise2的结果}})}// 此处与前一个if块的逻辑几乎相同,区别在于所调用的是onRejected函数,就不再做过多解释if (self.status === 'rejected') {return promise2 = new Promise(function(resolve, reject) {try {var x = onRejected(self.data)if (x instanceof Promise) {x.then(resolve, reject)}} catch (e) {reject(e)}})}if (self.status === 'pending') {// 如果当前的Promise还处于pending状态,我们并不能确定调用onResolved还是onRejected,// 只能等到Promise的状态确定后,才能确实如何处理。// 所以我们需要把我们的**两种情况**的处理逻辑做为callback放入promise1(此处即this/self)的回调数组里// 逻辑本身跟第一个if块内的几乎一致,此处不做过多解释return promise2 = new Promise(function(resolve, reject) {self.onResolvedCallback.push(function(value) {try {var x = onResolved(self.data)if (x instanceof Promise) {x.then(resolve, reject)}} catch (e) {reject(e)}})self.onRejectedCallback.push(function(reason) {try {var x = onRejected(self.data)if (x instanceof Promise) {x.then(resolve, reject)}} catch (e) {reject(e)}})})}
}// 为了下文方便,我们顺便实现一个catch方法
Promise.prototype.catch = function(onRejected) {return this.then(null, onRejected)
}复制代码
最后附上完整的代码:
Promise.prototype.then = function(onResolved, onRejected) {var self = thisvar promise2// 根据标准,如果then的参数不是function,则我们需要忽略它,此处以如下方式处理onResolved = typeof onResolved === 'function' ? onResolved : function(value) {}onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {}if (self.status === 'resolved') {// 如果promise1(此处即为this/self)的状态已经确定并且是resolved,我们调用onResolved// 因为考虑到有可能throw,所以我们将其包在try/catch块里return promise2 = new Promise(function(resolve, reject) {try {var x = onResolved(self.data)if (x instanceof Promise) { // 如果onResolved的返回值是一个Promise对象,直接取它的结果做为promise2的结果x.then(resolve, reject)}resolve(x) // 否则,以它的返回值做为promise2的结果} catch (e) {reject(e) // 如果出错,以捕获到的错误做为promise2的结果}})}// 此处与前一个if块的逻辑几乎相同,区别在于所调用的是onRejected函数,就不再做过多解释if (self.status === 'rejected') {return promise2 = new Promise(function(resolve, reject) {try {var x = onRejected(self.data)if (x instanceof Promise) {x.then(resolve, reject)}} catch (e) {reject(e)}})}if (self.status === 'pending') {// 如果当前的Promise还处于pending状态,我们并不能确定调用onResolved还是onRejected,// 只能等到Promise的状态确定后,才能确实如何处理。// 所以我们需要把我们的**两种情况**的处理逻辑做为callback放入promise1(此处即this/self)的回调数组里// 逻辑本身跟第一个if块内的几乎一致,此处不做过多解释return promise2 = new Promise(function(resolve, reject) {self.onResolvedCallback.push(function(value) {try {var x = onResolved(self.data)if (x instanceof Promise) {x.then(resolve, reject)}} catch (e) {reject(e)}})self.onRejectedCallback.push(function(reason) {try {var x = onRejected(self.data)if (x instanceof Promise) {x.then(resolve, reject)}} catch (e) {reject(e)}})})}
}// 为了下文方便,我们顺便实现一个catch方法
Promise.prototype.catch = function(onRejected) {return this.then(null, onRejected)
}复制代码
用es5实现es6的promise,彻底搞懂promise的原理相关推荐
- ES6学习——一文搞懂ES6
ES6学习--一文搞懂ES6 es6介绍 ES全称EcmaScript,是脚本语言的规范,而平时经常编写的EcmaScript的一种实现,所以ES新特性其实就是指JavaScript的新特性. 为什么 ...
- php service原理,轻松搞懂WebService工作原理
用更简单的方式给大家谈谈WebService,让你更快更容易理解,希望对初学者有所帮助. WebService是基于网络的.分布式的模块化组件. 我们直接来看WebService的一个简易工作流程: ...
- Android 动态分区详解(一) 5 张图让你搞懂动态分区原理
文章目录 0. 导读 1. 动态分区详解的背景 1.1 背景 1.2 动态分区的本质 2. Linux device mapper 驱动 3. Android 动态分区布局 3.1 动态分区布局 3. ...
- 【前端知识点】promise简书-30分钟带你搞懂promise面试必备
前言 写作初衷 本书的目的是以目前还在制定中的ECMAScript 6 Promises规范为中心,着重向各位读者介绍JavaScript中对Promise相关技术的支持情况. 通过阅读本书,我们希望 ...
- 这次彻底搞懂 Promise(手写源码多注释篇)
作者:一阵风,一枚只想安静写代码的程序员,来自程序员成长指北交流群 github: https://github.com/yizhengfeng-jj/promise 前言 promise 是 ...
- 异步编程:一次搞懂Promise,async,await
文章目录 前言 一.回调函数 二.Promise 三.错误处理 四.async/await await使用时的陷阱 1 2 3 总结 前言 异步编程允许我们在执行一个长时间任务时,程序不需要进行等待, ...
- 一首歌带你搞懂Promise
Promise 是异步编程的一种解决方案,比传统的解决方案--回调函数和事件--更合理和更强大.(摘自<ECMAScript 6 入门>),但是对于Promise的创建和执行流程,其实有很 ...
- 别不承认!搞懂那些数理原理,才发现它们和枯燥根本不沾边!
▲ 点击查看 数理化的学习对于很多孩子,包括家长都是一个大难题. 比如,我们要教孩子认识动物,一般是要给孩子看动物的图片或实体,孩子自然就对这个动物有个认知. 要教孩子数字,就会用一件玩具.两个苹果这 ...
- reactrouter监听路由变化_一篇文章搞懂前端路由原理解析和实现方式
在单页应用如此流行的今天,曾经令人惊叹的前端路由已经成为各大框架的基础标配,每个框架都提供了强大的路由功能,导致路由实现变的复杂. 想要搞懂路由内部实现还是有些困难的,但是如果只想了解路由实现基本原理 ...
最新文章
- 【云计算】5_云存储产品介绍
- java multivaluemap_java – 使用自定义值集合类型创建Commons Collections MultiValueMap
- Python基础day08 作业解析【7道 面向对象题目】
- Unity3d Fast Indirect illumination Using Two Virtual Spherical Gaussian Lights-Square Enix论文
- 几种常用的优化方法梯度下降法、牛顿法、)
- Sql Server2005对t-sql的增强之Cross Apply
- 发一个多CPU中进程与CPU绑定的例子
- Java正则速成秘籍(一)之招式篇
- 汇编学习--6.13--基础知识
- vue-router学习第一天
- 使用 MyEclipse远程调试 Java 应用程序
- 手机远程控制电脑方法(TeamViewer vs 向日葵)
- 计算机网络——模拟信号(四)
- 阿里淘系优质开源项目推荐(下)
- 用CSS画小猪佩奇,你就是下一个社会人!
- 【深度域自适应】DANN梯度反转层(GRL)理解
- 基于SSH的校园二手物品交易系统
- 利用百度网盘引流方法,如何利用百度网盘进行精准引流?
- It's a test
- LayIM 3.9.1与ASP.NET SignalR实现Web聊天室快速入门(七)之LayIM与MVC数据交互实现单聊和群聊
热门文章
- 想学IT的必看!深度解析跳槽从开始到结束完整流程万字长文!
- 计算机科学渗透信息论的思想,信息系统思想在高中地理课程教学中的渗透方法分析...
- java多线程安全解决方案_《Java多线程编程核心技术(第2版)》 —1.2.8 实例变量共享造成的非线程安全问题与解决方案...
- 构造java_Java构造方法
- 机器人绳索英雄下载苹果手机_机器人绳索英雄
- 判断php对象不包含的key,如何判断一个对象是否包含指定Key
- Vue+Vue Router+Webpack打包网站基础页面
- 我的世界服务器不显示浮空字,我的世界服务器浮空字怎么做 | 手游网游页游攻略大全...
- html保存为svg,JavaScript – 将内联SVG保存为JPEG / PNG / SVG
- python同时发大量请求_Python批量发送post请求的实现代码