Promise 简介

(该部分转载自 blog.csdn.net/u010576399/… ) Promise对象是CommonJS工作组提出的一种规范,目的是为异步操作提供统一接口.

那么,什么是Promises?

首先,它是一个对象,也就是说与其他JavaScript对象的用法,没有什么两样;其次,它起到代理作用(proxy),充当异步操作与回调函数之间的中介。它使得异步操作具备同步操作的接口,使得程序具备正常的同步运行的流程,回调函数不必再一层层嵌套。

简单说,它的思想是,每一个异步任务立刻返回一个Promise对象,由于是立刻返回,所以可以采用同步操作的流程。这个Promises对象有一个then方法,允许指定回调函数,在异步任务完成后调用。

比如,异步操作f1返回一个Promise对象,它的回调函数f2写法如下:

(new Promise(f1)).then(f2);
复制代码

这种写法对于多层嵌套的回调函数尤其方便。

// 传统写法
step1(function (value1) {step2(value1, function(value2) {step3(value2, function(value3) {step4(value3, function(value4) {// ...});});});
});// Promises的写法
(new Promise(step1)).then(step2).then(step3).then(step4);
复制代码

从上面代码可以看到,采用Promises接口以后,程序流程变得非常清楚,十分易读。

注意,为了便于理解,上面代码的Promise对象的生成格式,做了简化。

总的来说,传统的回调函数写法使得代码混成一团,变得横向发展而不是向下发展。Promises规范就是为了解决这个问题而提出的,目标是使用正常的程序流程(同步),来处理异步操作。它先返回一个Promise对象,后面的操作以同步的方式,寄存在这个对象上面。等到异步操作有了结果,再执行前期寄放在它上面的其他操作。

Promises原本只是社区提出的一个构想,一些外部函数库率先实现了这个功能。ECMAScript 6将其写入语言标准,因此目前JavaScript语言原生支持Promise对象。

手动实现一个满足promises-aplus-tests的Promise

promisesaplus.com/ 是一个介绍promise如何实现的一个网站. 根据该网站提供的介绍信息, 我们可以尝试自己写一个promise并使用提供的promises-aplus-tests工具对所写的promise进行测试.

Step1

首先 实现promise最基本的功能: 即在promise创建以后执行执行器中的代码,在then的时候相应的函数得以执行. 需要注意的是,当执行器中有错误抛出的时候,应该捕获错误并直接执行reject

//实现功能: 当执行器中调用resolve(),则then中只执行onFulfiled方法,执行器中调用reject(),则then中只执行onRejected方法, 当两个方法都有的时候,以先执行的方法为准,后执行的方法对then不产生影响.
function Promise (executor) {   //执行器 let self = this;self.status = 'pending';   //引入状态,对两个方法都有的情况进行区分self.value = undefined;    //默认值self.reason = undefined;function resolve(data_value) {if(self.status === 'pending') {self.status = 'resolved';self.value = data_value;}}function reject(data_reason) {if(self.status === 'pending') {self.status = 'rejected';self.reason = data_reason;}}//如果executor是同步代码 进行try catch获取其中的异常 如果有异常 把异常传到rejecttry {executor(resolve, reject);   } catch (e) {  reject(e);                 //调用reject并把捕获的error作为参数传给reject}
}Promise.prototype.then = function (onFulfiled, onRejected) {let self = this;if(self.status === 'resolved') {onFulfiled(self.value);}if(self.status === 'rejected') {onRejected(self.value);}
}
复制代码

step2

那么问题来了, 如果执行器中有异步代码, 上面的实现方法就会出问题,因为在执行

executor(resolve, reject);
复制代码

的时候,异步代码不会立即执行, then中没有判断异步代码是否已经执行的机制. 我们的解决方案是: 在then中判定状态是否为pending,如果状态为pending(此时为构造函数new Promise()执行期间), 则把then中的执行函数onFulfiled, onRejected先存入队列(用array实现),当状态改变后执行这些方法.

function Promise (executor) {   //执行器 let self = this;self.status = 'pending';self.value = undefined;    //默认值self.reason = undefined;self.onResolvedCallbacks = [];   //存放then成功的回调 数组self.onRejectedCallbacks = [];   //存放then失败的回调 数组function resolve(data_value) {if(self.status === 'pending') {self.status = 'resolved';self.value = data_value;self.onResolvedCallbacks.forEach(function(fn) {   //调用resolve的时候执行保存在onRejectedCallbacks的函数fn();})}}function reject(data_reason) {if(self.status === 'pending') {self.status = 'rejected';self.reason = data_reason;self.onRejectedCallbacks.forEach(function(fn) {   //调用resolve的时候执行保存在onRejectedCallbacks的函数fn();})}}try {executor(resolve, reject);   //当executor中有异步代码时 这部分不会立即执行(但是前面的部分在new的时候还是会执行)} catch (e) {  reject(e);                 }
}Promise.prototype.then = function (onFulfiled, onRejected) {let self = this;if(self.status === 'resolved') {onFulfiled(self.value);}if(self.status === 'rejected') {onRejected(self.reason);}//当调用then时可能没成功 也没失败if(self.status === 'pending') {             //此时没有resolve也没有rejectself.onResolvedCallbacks.push(function(){       //用数组是为了保证在异步时有多次promise.then的情况 onFulfiled(self.value);});self.onRejectedCallbacks.push(function(){onRejected(self.reason);});}
}
复制代码

step3

到现在为止,我们仅仅完成了promise最基本的功能, 但是promise中一个重要的功能: then的链式调用尚未实现. 解决的思路类似于jquery的链式调用, 区别则是:jquery是返回this 这里则是返回一个新的promise. 我们在then中新建一个变量promise2. 以

self.status === 'resolved'
复制代码

情况为例 将之前的代码修改为:

if(self.status === 'resolved') {promise2 = new Promise(function(resolve, reject){  //将前一次then中的执行函数放入新的Promise的executor得到promise2作为下次then的返回值onFulfiled(self.value);           //注意这时返回的promise的执行器执行的是onFulfiled函数 而不是resolve或者reject. }) }
复制代码

但是以上代码中, promise2在创建的时候, 并没有设定resolve/reject的规则,因此只能算作半成品. 根据规定, 如果onFulfile/onRejected有返回值, 则将返回值作为resolve/reject的参数传入,这样,下一次.then就有状态,不再是无根之木. 根据返回值的不同, 又将返回值分别以普通值和promise进行分别处理. 为此我们引入了一个统一的处理方法,以resolve()为例, 对应的处理方法我们取名为resolvePromise(promise2, x, resolve, reject) 其中x为onFulfiled的返回值. resolvePromise代码如下所示:

function resolvePromise(promise2, x, resolve, reject) {//有可能这里返回的x是别人的promise 要尽可能允许其他人乱写 if(promise2 === x) {//这里应该报一个循环引用的类型错误return reject(new TypeError('循环引用'));}//看x是不是一个promise promise应该是一个对象if (x!== null && (typeof x ==='object' ||typeof x === 'function')) {//可能是promise 看这个对象中是否有then 如果有 姑且作为promise 用try catch防止报错try {let then = x.then;if (typeof then === 'function') {then.call(x, function(y) {//成功resolvePromise(promise2, y, resolve, reject)}, function (err) {//失败reject(err);})} else {resolve(x)             //如果then不是函数 则把x作为返回值.}} catch (e) {reject(e)}} else {  //普通值return resolve(x)}
}
复制代码

相应的

self.status === 'resolved'
复制代码

时的代码则为:

if(self.status === 'resolved') {promise2 = new Promise(function(resolve, reject){  let x = onFulfiled(self.value);    resolvePromise(promise2, x, resolve, reject);}) }
复制代码

为例保证代码的通用性,考虑到有可能其他人的promise写的不正确,可能会既调用成功又调用失败的情况, 我们应当在代码中对返回的promise进行判断: 如果两个都调用 先调用谁 另一个忽略掉. 为此引入一个变量called.

function resolvePromise(promise2, x, resolve, reject) {//有可能这里返回的x是别人的promise 要尽可能允许其他人乱写 if(promise2 === x) {//这里应该报一个循环引用的类型错误return reject(new TypeError('循环引用'));}//看x是不是一个promise promise应该是一个对象let called;  //表示是否调用过成功或者失败if (x!== null && (typeof x ==='object' ||typeof x === 'function')) {//可能是promise 看这个对象中是否有then 如果有 姑且作为promise 用try catch防止报错try {let then = x.then;if (typeof then === 'function') {//成功then.call(x, function(y) {if (called) returncalled = true;resolvePromise(promise2, y, resolve, reject)}, function(err) {if (called) returncalled = true;reject(err);})} else {resolve(x)             //如果then不是函数 则把x作为返回值.}} catch (e) {if (called) returncalled = true;reject(e)}} else {  //普通值return resolve(x)}
}
复制代码

Step4

到目前为止, promise的大框架基本完成. 接下来需要解决的两个小问题是值的穿透以及onFulfiled/onRejected的异步执行问题. 值的穿透的含义就是, 当then中使用没有任何方法, onFulfiled()中的data自动作为返回值. 实现起来也很简单, 在then的定义的最开始部分做一个判断:

Promise.prototype.then = function (onFulfiled, onRejected) {//成功和失败默认不传, 给一个默认函数 可以实现值的穿透onFulfiled = typeof onFulfiled === 'function'? onFulfiled:function(value) {return value;}onRejected = typeof onRejected === 'function'? onRejected:function(err) {throw err;           //在值的穿透的情况下 应该走下一个then的onRejected而不是onFulfiled 保证逻辑的一致性}.....
}
复制代码

对于onFulfild/onRejected异步执行的问题, 则是在

if(self.status === 'resolved') {promise2 = new Promise(function(resolve, reject){let x = onFulfiled(self.value);                             resolvePromise(promise2, x, resolve, reject) ...}
}
复制代码

代码块使用setTimeout. 这时带来的副作用就是之前在promise构造函数中的代码块

try {executor(resolve, reject);   } catch (e) {  reject(e);                 }
复制代码

不能捕获到setTimeout中的异步函数, 因此需要在setTimeout中也需要包一层try/catch:

if(self.status === 'resolved') {promise2 = new Promise(function(resolve, reject){  setTimeout(function(){                          //用setTimeOut实现异步try {let x = onFulfiled(self.value);        //x可能是普通值 也可能是一个promise, 还可能是别人的promise                               resolvePromise(promise2, x, resolve, reject)  //写一个方法统一处理 } catch (e) {reject(e);                                        }}) }) }
复制代码

step5

最终代码如下所示:

function Promise (executor) {   let self = this;self.status = 'pending';self.value = undefined;    self.reason = undefined;self.onResolvedCallbacks = [];   self.onRejectedCallbacks = [];   function resolve(data_value) {if(self.status === 'pending') {self.status = 'resolved';self.value = data_value;self.onResolvedCallbacks.forEach(function(fn) {  fn();})}}function reject(data_reason) {if(self.status === 'pending') {self.status = 'rejected';self.reason = data_reason;self.onRejectedCallbacks.forEach(function(fn) {  fn();})}}try {executor(resolve, reject);   } catch (e) {  reject(e);                 }
}function resolvePromise(promise2, x, resolve, reject) {//有可能这里返回的x是别人的promise 要尽可能允许其他人乱写 if(promise2 === x) {//这里应该报一个循环引用的类型错误return reject(new TypeError('循环引用'));}//看x是不是一个promise promise应该是一个对象let called;  //表示是否调用过成功或者失败if (x!== null && (typeof x ==='object' ||typeof x === 'function')) {//可能是promise 看这个对象中是否有then 如果有 姑且作为promise 用try catch防止报错try {let then = x.then;if (typeof then === 'function') {//成功then.call(x, function(y) {if (called) return        //避免别人写的promise中既走resolve又走reject的情况called = true;resolvePromise(promise2, y, resolve, reject)}, function(err) {if (called) returncalled = true;reject(err);})} else {resolve(x)             //如果then不是函数 则把x作为返回值.}} catch (e) {if (called) returncalled = true;reject(e)}} else {  //普通值return resolve(x)}}Promise.prototype.then = function (onFulfiled, onRejected) {//成功和失败默认不传给一个函数onFulfiled = typeof onFulfiled === 'function'? onFulfiled:function(value) {return value;}onRejected = typeof onRejected === 'function'? onRejected:function(err) {throw err;}let self = this;let promise2;  //新增: 返回的promiseif(self.status === 'resolved') {promise2 = new Promise(function(resolve, reject){  setTimeout(function(){                          //用setTimeOut实现异步try {let x = onFulfiled(self.value);        //x可能是普通值 也可能是一个promise, 还可能是别人的promise                               resolvePromise(promise2, x, resolve, reject)  //写一个方法统一处理 } catch (e) {reject(e);                                        }}) }) }if(self.status === 'rejected') {promise2 = new Promise(function(resolve, reject){setTimeout (function() {try {let x = onRejected(self.reason);resolvePromise(promise2, x, resolve, reject)} catch (e) {reject(e);}})}) }if(self.status === 'pending') {            promise2 = new Promise (function(resolve, reject) {   self.onResolvedCallbacks.push(function(){   setTimeout(function(){try {let x = onFulfiled(self.value); resolvePromise(promise2, x, resolve, reject)} catch (e) {reject(e);}   })});self.onRejectedCallbacks.push(function(){setTimeout(function(){try {let x = onRejected(self.reason);resolvePromise(promise2, x, resolve, reject)} catch (e) {reject(e);} })   });})}return promise2;
}
复制代码

使用promises-aplus-tests对该方法进行测试, 最终测试得到通过. 如图所示:

test result

当然, 该promise相对于原生的promise还有一些不同,比如没有实现catch功能,没有静态方法等. 这些部分我们将下次进行详细讨论.

手动实现一个满足promises-aplus-tests的Promise相关推荐

  1. 如何用JavaScript手动实现一个栈

    什么是栈(Stack) 栈是一种遵从后进先出(LIFO)原则的有序集合. 新添加的或待删除的元素都保存在栈的末尾,称为栈顶,另一端叫栈底. 在栈里,新元素都靠近栈顶,旧元素都接近栈底 现实中的例子 在 ...

  2. uglifyjs报错 webpack_基于vue2.X的webpack基本配置,教你手动撸一个webpack4的配置

    webpack说复杂也不复杂.不复杂,核心概念不外乎是entry, output, loader, plugins.webpack4还新增了optimization选项,用于代码分割和打包优化.现在w ...

  3. 从原理到实践手动拼凑一个Linux系统

    从原理到实践手动拼凑一个Linux系统 转载于:https://blog.51cto.com/suninger123/1371464

  4. 三、如何手动实现一个微前端框架雏形

    如何手动实现一个微前端框架雏形 一.了解微前端 1. 什么是微前端 为了解决一整块儿庞大的前端服务所带来的变更和拓展方面的限制,将整体前端服务拆分成一些更小.更简单的,能够独立开发.测试部署的小块儿. ...

  5. vsc写vue生成基本代码快捷键_基于vue2.X的webpack基本配置,教你手动撸一个webpack4的配置...

    webpack说复杂也不复杂.不复杂,核心概念不外乎是entry, output, loader, plugins.webpack4还新增了optimization选项,用于代码分割和打包优化.现在w ...

  6. 原来热加载如此简单,手动写一个 Java 热加载吧

    1. 什么是热加载 热加载是指可以在不重启服务的情况下让更改的代码生效,热加载可以显著的提升开发以及调试的效率,它是基于 Java 的类加载器实现的,但是由于热加载的不安全性,一般不会用于正式的生产环 ...

  7. JS手动实现一个new操作符

    要手动实现一个 new 操作符,首先要知道 new 操作符都做了什么事,即构造函数的内部原理: 1.创建一个新对象: 2.链接到原型(将构造函数的 prototype 赋值给新对象的 __proto_ ...

  8. Servlet→简介、手动编写一个Servlet、使用MyEclipse编写一个Servlet、Servlet生命周期、路径编写规范、初始化参数、MVC设计模式

    手动编写一个Servlet 使用MyEclipse编写一个Servlet Servlet生命周期 路径编写规范 初始化参数 MVC设计模式 企者不立:跨者不行. 自见者不明: 自是者不彰: 自伐者无功 ...

  9. 栈和递归---手动实现一个栈和蒜头君吃桃

    栈 push--压入 pop--弹出 特点:先进后出 一.手动实现一个栈 封装数据结构--栈写成class或struct,将当前栈的数据和对当前栈的操作都放在里面 定义一个结构体Stack,规定最大存 ...

最新文章

  1. CC讲坛-大脑疾病背后的秘密-许执恒
  2. 迎战双十一,阿里集聚500多家外部合作伙伴进行系统压力测试
  3. 提高跑步速度的3个方法
  4. matlab删失数据威布尔,基于混合I型删失数据威布尔模型的可接受抽样计划
  5. iQOO3Android11稳定版,vivo安卓11来了iQOO3 NEX3S尝鲜Androi11测试版!
  6. SpringBoot自学汇总
  7. html标签的嵌套规则有哪些,html 标签的嵌套规则
  8. base64 二进制流java_读取和base64编码二进制文件
  9. Unity5.1 新的网络引擎UNET(十五) Networking 引用--下
  10. 我的测试生活感悟4 - 谈谈面试
  11. 复杂json解析(json里面嵌套json)
  12. 分享个markdownpad2的授权key
  13. 服务器数码管不显示,LED数码管不亮的原因及故障排查方法
  14. C语言正交表测试用例,用正交表设计测试用例
  15. 责任链模式实现及在Filter中的应用
  16. python实现 stft_Python中可转换的STFT和ISTFT
  17. 幼儿园计算机课件制作,幼儿园课件制作工具
  18. linux java模拟器_在Android模拟器和Ubuntu上测试Linux驱动
  19. MySQL安装当中遇到的问题
  20. 终端连接阿里云服务器出现Permission denied (publickey)解决方法

热门文章

  1. zabbix 监控 db2_二十多款开源的服务器监控软件,你用过几款? – 阿汤博客
  2. Button上文本在切换英文后,默认为全部大写
  3. Ni Multisim 101序列检测器实验报告
  4. 【AI特训营】:柯西分布 Paddle API实现
  5. Pandas(一)--Series结构
  6. PTA团体程序设计天梯赛-练习集L1-021 重要的话说三遍
  7. 作为一名工程师,你应该专注于成为一名多面手还是专家?
  8. android gta5 下载地址,GTA 5 for Android下载
  9. 、再烦,也别忘记微笑;再急,也要注意语气;.再苦,也别忘坚持;再累,也要爱自己。
  10. c语言文件操作步骤是,文件操作的正确流程,C语言文件操作的函数