Promise简单实现(正常思路版)
转自: http://www.jianshu.com/p/473cd754311f
Promise
看了些promise的介绍,还是感觉不够深入,这个在解决异步问题上是一个很好的解决方案,所以详细看一下,顺便按照自己的思路实现一个简单的Promise。
Promise/A+规范:
- 首先重新阅读了下A+的规范:
- promise代表了一个异步操作的最终结果,主要是通过then方法来注册成功以及失败的情况,
- Promise/A+历史上说是实现了Promise/A的行为并且考虑了一些不足之处,他并不关心如何创建,完成,拒绝Promise,只考虑提供一个可协作的then方法。
术语:
promise
是一个拥有符合上面的特征的then方法的对象或者方法。thenable
是定义了then方法的对象或者方法value
是任何合法的js的值(包括undefined,thenable或者promise)exception
是一个被throw申明抛出的值reason
是一个指明了为什么promise被拒绝
状态要求:
- promise必须是在pending,fulfilled或者rejected之间的一种状态。
- promise一旦从pending变成了fulfilled或则rejected,就不能再改变了。
- promise变成fulfilled之后,必须有一个value,并且不能被改变
- promise变成rejected之后,必须有一个reason,并且不能被改变
then方法的要求:
- promise必须有个then方法来接触当前的或者最后的value或者reason
- then方法接受两个参数,onFulfilled和onRejected,这两个都是可选的,如果传入的不是function的话,就会被忽略
- 如果onFulfilled是一个函数,他必须在promise完成后被执行(不能提前),并且value是第一个参数,并且不能被执行超过一次
- 如果onRejected是一个函数,他必须在promise拒绝后被执行(不能提前),并且reason是第一个参数,并且不能被执行超过一次
- onFulfilled或者onRejected只能在执行上下文堆只包含了平台代码的时候执行(就是要求onfulfilled和 onrejected必须异步执行,必须在then方法被调用的那一轮事件循环之后的新执行栈执行,这里可以使用macro-task或者micro- task,这两个的区别参见文章)
- onFulfilled或者onRejected必须作为function被执行(就是说没有一个特殊的this,在严格模式中,this就是undefined,在粗糙的模式,就是global)
- then方法可能在同一个promise被调用多次,当promise被完成,所有的onFulfilled必须被顺序执行,onRejected也一样
then方法必须也返回一个promise(这个promise可以是原来的promise,实现必须申明什么情况下两者可以相等)promise2 = promise1.then(onFulfilled, onRejected);
- 如果
onFulfilled
和onRejected
都返回一个value x,执行2.3Promise的解决步骤[Resolve] - 如果
onFulfilled
和onRejected
都抛出exception e,promise2必须被rejected同样的e - 如果
onFulfilled
不是个function,且promise1 is fulfilled,promise2也会fulfilled,和promise1的值一样 - 如果
onRejected
不是个function,且promise1 is rejected,promise2也会rejected,理由和promise1一样
这里不论promise1被完成还是被拒绝,promise2 都会被 resolve的,只有出现了一些异常才会被rejected
- 如果
Promise的解决步骤==[Resolve]
- 这个是将
promise
和一个值x
作为输入的一个抽象操作。如果这个x是支持then的,他会尝试让promise接受x的状态;否则,他会用x的值来fullfill这个promise。运行这样一个东西,遵循以下的步骤- 如果promise和x指向同一个对象,则reject这个promise使用TypeError。
- 如果x是一个promise,接受他的状态
- 如果x在pending,promise必须等待x的状态改变
- 如果x被fullfill,那么fullfill这个promise使用同一个value
- 如果x被reject,那么reject这个promise使用同一个理由
- 如果x是一个对象或者是个方法
- 如果x.then返回了错误,则reject这个promise使用错误。
- 如果then是一个方法,使用x为this,resolvePromise为一参,rejectPromise为二参,
- 如果resolvePromise被一个值y调用,那么运行[Resolve]
- 如果rejectPromise被reason r,使用r来reject这个promise
- 如果resolvePromise和rejectPromise都被调用了,那么第一个被调用的有优先权,其他的beihulue
- 如果调用then方法得到了exception,如果上面的方法被调用了,则忽略,否则reject这个promise
- 如果then方法不是function,那么fullfill这个promise使用x
- 如果x不是一个对象或者方法,那么fullfill这个promise使用x
如果promise产生了环形的嵌套,比如[Resolve]最终唤起了[Resolve],那么实现建议且并不强求来发现这种循环,并且reject这个promise使用一个TypeError。
接下来正式写一个promise
思路都是最正常的思路,想要写一个Promise,肯定得使用一个异步的函数,就拿setTimeout来做。
var p = new Promise(function(resolve){
setTimeout(resolve, 100);
});
p.then(function(){console.log('success')},function(){console.log('fail')});
初步构建
上面是个最简单的使用场景我们需要慢慢来构建
function Promise(fn){
//需要一个成功时的回调
var doneCallback;
//一个实例的方法,用来注册异步事件
this.then = function(done){
doneCallback = done;
}
function resolve(){
doneCallback();
}
fn(resolve);
}
加入链式支持
下面加入链式,成功回调的方法就得变成数组才能存储
function Promise(fn){
//需要成功以及成功时的回调
var doneList = [];
//一个实例的方法,用来注册异步事件
this.then = function(done ,fail){
doneList.push(done);
return this;
}
function resolve(){
doneList.forEach(function(fulfill){
fulfill();
});
}
fn(resolve);
}
这里promise里面如果是同步的函数的话,doneList里面还是空的,所以可以加个setTimeout来将这个放到js的最后执行。这里主要是参照了promiseA+的规范,就像这样
function resolve(){
setTimeout(function(){
doneList.forEach(function(fulfill){
fulfill();
});
},0);
}
加入状态机制
这时如果promise已经执行完了,我们再给promise注册then方法就怎么都不会执行了,这个不符合预期,所以才会加入状态这种东西。更新过的代码如下
function Promise(fn){
//需要成功以及成功时的回调
var state = 'pending';
var doneList = [];
//一个实例的方法,用来注册异步事件
this.then = function(done){
switch(state){
case "pending":
doneList.push(done);
return this;
break;
case 'fulfilled':
done();
return this;
break;
}
}
function resolve(){
state = "fulfilled";
setTimeout(function(){
doneList.forEach(function(fulfill){
fulfill();
});
},0);
}
fn(resolve);
}
加上异步结果的传递
现在的写法根本没有考虑异步返回的结果的传递,我们来加上结果的传递
function resolve(newValue){
state = "fulfilled";
var value = newValue;
setTimeout(function(){
doneList.forEach(function(fulfill){
value = fulfill(value);
});
},0);
}
支持串行
这样子我们就可以将then每次的结果交给后面的then了。但是我们的promise现在还不支持promise的串行写法。比如我们想要
var p = new Promise(function(resolve){
setTimeout(function(){
resolve(12);
}, 100);
});
var p2 = new Promise(function(resolve){
setTimeout(function(){
resolve(42);
}, 100);
});
p.then(
function(name){
console.log(name);return 33;
}
)
.then(function(id){console.log(id)})
.then(p2)
.then(function(home){console.log(home)});
所以我们必须改下then方法。
当then方法传入一般的函数的时候,我们目前的做法是将它推进了一个数组,然后return this来进行链式的调用,并且期望在resolve方法调用时执行这个数组。
最开始我是研究的美团工程师的一篇博客,到这里的时候发现他的解决方案比较跳跃,于是我就按照普通的正常思路先尝试了下:
如果传入一个promise的话,我们先尝试继续推入数组中,在resolve的地方进行区分,发现是可行的,我先贴下示例代码,然后会有详细的注释。
function Promise(fn){
//需要成功以及成功时的回调
var state = 'pending';
var doneList = [];
this.then = function(done){
switch(state){
case "pending":
doneList.push(done);
return this;
break;
case 'fulfilled':
done();
return this;
break;
}
}
function resolve(newValue){
state = "fulfilled";
setTimeout(function(){
var value = newValue;
//执行resolve时,我们会尝试将doneList数组中的值都执行一遍
//当遇到正常的回调函数的时候,就执行回调函数
//当遇到一个新的promise的时候,就将原doneList数组里的回调函数推入新的promise的doneList,以达到循环的目的
for (var i = 0;i<doneList.length;i++){
var temp = doneList[i](value)
if(temp instanceof Promise){
var newP = temp;
for(i++;i<doneList.length;i++){
newP.then(doneList[i]);
}
}else{
value = temp;
}
}
},0);
}
fn(resolve);
}
var p = function (){
return new Promise(function(resolve){
setTimeout(function(){
resolve('p 的结果');
}, 100);
});
}
var p2 = function (input){
return new Promise(function(resolve){
setTimeout(function(){
console.log('p2拿到前面传入的值:' + input)
resolve('p2的结果');
}, 100);
});
}
p()
.then(function(res){console.log('p的结果:' + res); return 'p then方法第一次返回'})
.then(function(res){console.log('p第一次then方法的返回:'+res); return 'p then方法第二次返回'})
.then(p2)
.then(function(res){console.log('p2的结果:' + res)});
加入reject
我按照正常思路这么写的时候发现出了点问题,因为按照最上面的规范。即使一个promise被rejected,他注册的then方法之后再注册的 then方法会可能继续执行resolve的。即我们在then方法中为了链式返回的this的status是可能会被改变的,假设我们在实现中来改变状 态而不暴露出来(这其实一点也不推荐)。
我直接贴实现的代码,还有注释作为讲解
function Promise(fn){
var state = 'pending';
var doneList = [];
var failList= [];
this.then = function(done ,fail){
switch(state){
case "pending":
doneList.push(done);
//每次如果没有推入fail方法,我也会推入一个null来占位
failList.push(fail || null);
return this;
break;
case 'fulfilled':
done();
return this;
break;
case 'rejected':
fail();
return this;
break;
}
}
function resolve(newValue){
state = "fulfilled";
setTimeout(function(){
var value = newValue;
for (var i = 0;i<doneList.length;i++){
var temp = doneList[i](value);
if(temp instanceof Promise){
var newP = temp;
for(i++;i<doneList.length;i++){
newP.then(doneList[i],failList[i]);
}
}else{
value = temp;
}
}
},0);
}
function reject(newValue){
state = "rejected";
setTimeout(function(){
var value = newValue;
var tempRe = failList[0](value);
//如果reject里面传入了一个promise,那么执行完此次的fail之后,将剩余的done和fail传入新的promise中
if(tempRe instanceof Promise){
var newP = tempRe;
for(i=1;i<doneList.length;i++){
newP.then(doneList[i],failList[i]);
}
}else{
//如果不是promise,执行完当前的fail之后,继续执行doneList
value = tempRe;
doneList.shift();
failList.shift();
resolve(value);
}
},0);
}
fn(resolve,reject);
}
var p = function (){
return new Promise(function(resolve,reject){
setTimeout(function(){
reject('p 的结果');
}, 100);
});
}
var p2 = function (input){
return new Promise(function(resolve){
setTimeout(function(){
console.log('p2拿到前面传入的值:' + input)
resolve('p2的结果');
}, 100);
});
}
p()
.then(function(res){console.log('p的结果:' + res); return 'p then方法第一次返回'},function(value){console.log(value);return 'p then方法第一次错误的返回'})
.then(function(res){console.log('p第一次then方法的返回:'+res); return 'p then方法第二次返回'})
.then(p2)
.then(function(res){console.log('p2的结果:' + res)});
这篇文章是自己根据比较正常的思路来写的一个简单的promise。
接下来还会有篇文章来自习研究下美团那篇博客以及一些主流的promise的写法,敬请期待。
参考:
先写到这里,顺便给个github的传送门,喜欢的朋友star一下啊,自己平时遇到的问题以及一下学习的经历及写代码的思考都会在github上进行记录~
Promise简单实现(正常思路版)的更多相关文章
- ES6 promise简单实现
基本功能实现: function Promise(fn){ //需要一个成功时的回调 var doneCallback; //一个实例的方法,用来注册异步事件 this.then = function ...
- es6 Promise简单介绍
promise的基本用法 promise执行多步操作非常好用,那我们就来模仿一个多步操作的过程,那就以吃饭为例吧.要想在家吃顿饭,是要经过三个步骤的. 洗菜做饭. 坐下来吃饭. 收拾桌子洗碗. 这个过 ...
- 编写函数求整形数组a中存储的m个不重复的整数的第k大的整数(其中m>=1,1<=k<=m)很简单的一个思路是酱紫的:管他辣么多干啥,上来一把排序然后直接得答案
/** * @author:(LiberHome) * @date:Created in 2019/2/28 20:38 * @description: * @version:$ *//*编写函数求整 ...
- 理解Promise简单实现的背后原理
在写javascript时我们往往离不开异步操作,过去我们往往通过回调函数多层嵌套来解决后一个异步操作依赖前一个异步操作,然后为了解决回调地域的痛点,出现了一些解决方案比如事件订阅/发布的.事件监听的 ...
- web storage 简单的网页留言版
html <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <ti ...
- Promise简单实现--摘抄
Promise 看了些promise的介绍,还是感觉不够深入,这个在解决异步问题上是一个很好的解决方案,所以详细看一下,顺便按照自己的思路实现一个简单的Promise. Promise/A+规范: 首 ...
- SignalR简单示例教程入门版
上周五最后一天在公司上班,无聊之余就想做点什么.介于之前有人让我做个简易版的在线聊天的,于是乎就打算花一天时间来弄下关于SignalR的简单教程制作一个在线的聊天的. 1:前端用了国产的一个MVVM框 ...
- JavaScript案例六:简单省市联动(NBA版)
JavaScript实现简单省市(NBA版)联动 <!DOCTYPE html> <html> <head> <title>JavaScript实现简单 ...
- 小米Note 2简单卡刷开发版启用root超级权限的步骤
小米的机器不同手机型号一般MIUI官方论坛都提供两个不同版本,分别为稳定版和开发版,稳定版没有提供Root超级权限管理,开发版中就开启了Root超级权限,较多时候我们需要使用的一些功能强大的软件,都需 ...
随机推荐
- 简单实现KNN(处理连续型数据)
import numpy as np import matplotlib.pyplot as plt import time import math import collections raw_da ...
- css代码实现switch开关滑动
效果预览: 代码如下: <style> #toggle-button{ display: none; } .button-label{ position: relative; displa ...
- CTF伪协议+preg_replace()函数的代码执行
一道学习中所遇到的ctf 步骤: 我们点击题目链接,然后在页面先点击”云平台后台管理中心“ 然后url后面跟了参数page,题目提示了一个文件包含,我们试试index.php 看到了输出了ok,应该是 ...
- Java多线程之synchronized和volatile
概述 用Java来开发多线程程序变得越来越常见,虽然Java提供了并发包来简化多线程程序的编写,但是我们有必要深入研究一下,才能更好的掌握这块知识. 本文主要对Java提供的底层原语synchroni ...
- 浅谈C#委托的用法-delegate[转]
一.委托的概念 委托和类一样是一种用户自定义类型,它存储的就是一系列具有相同签名和返回类型的方法的地址,调用委托的时候,它所包含的所有方法都会被执行. 借用百度上的一句话概括:委托是一个类,它定义了方 ...
- 欢迎来到L T X的博客 & 博客转型公告
这里是L T X,一位来自重庆的学生的个人博客. 由于博主以前是OIer,目前博客里主要是OI相关的内容. 但是现在博主已经退役了,因此博客将会转向...嗯...那种...就是那种...比较奇怪的类型 ...
- php私有组件以及创建自己的composer私有组件(packagist+git+composer)
1.私有组件 大多数时候我们使用的都是公开可用的开源组件,但有时候如果公司使用内部开发的PHP组件,而基于许可证和安全方面的问题不能将其开源,就需要使用私有组件.对Composer而言,这是小菜一碟. ...
- macOS 下安装tomcat
The Servlet 4.0 specification is out and Tomcat 9.0.x does support it. Time to dive into Tomcat 9. [ ...
- Hibernate的save方法不能进行数据库插入
问题描述 在 MyEcplise 上运行 tomcat,利用 po 模板自动生成 po 文件,调用 po 的 save 方法,不报错,但是无法把数据插入数据库 applicationContext.x ...
- Vs2013以管理员身份运行
VS快捷方式目录下的devenv.exe 右键->兼容性疑难解答->疑难解答程序->勾选该程序需要附加权限即可,以后每次打开VS时都会以管理员身份运行了!