教你一步一步实现一个Promise
Promise我想现在大家都非常熟悉了,主要作用就是解决异步回调问题,这里简单介绍下。
Promise规范是CommonJS规范之一,而Promise规范又分了好多种,比如 Promises/A、Promises/B、Promises/Kiss等等
有兴趣的可以到这多了解一些 http://wiki.commonjs.org/wiki/Promises
现在比较流行的是Promise/A规范,人们对它的完善和扩展,逐渐形成了Promise/A+规范,A+已脱颖而出。
说到这里规范是什么,可以去这里了解下
http://promises-aplus.github.io/promises-spec/
http://hussion.me/2013/10/19/promises-a/
现在已有浏览器内置支持Promise,它的api语法可以在这里查看
http://www.html5rocks.com/zh/tutorials/es6/promises/#toc-api
可以看到它的api并不多,其实规范也不多,我觉的大致抓住几个重要的点就够了,
1、promise有三种状态,等待(pending)、已完成(fulfilled)、已拒绝(rejected)
2、promise的状态只能从“等待”转到“完成”或者“拒绝”,不能逆向转换,同时“完成”和“拒绝”也不能相互转换
3、promise必须有一个then方法,而且要返回一个promise,供then的链式调用,也就是可thenable的
4、then接受俩个回调(成功与拒绝),在相应的状态转变时触发,回调可返回promise,等待此promise被resolved后,继续触发then链
知道这几个重要的特点,我们就可以参考浏览器内置的api来实现了,
我们可以不必太受规范约束,先按照自己的想法来就好了。
promise的使用大致如下
var promise = new Promise(function(resolve, reject) {
setTimeout(function(){
resolve('val')
});
}); promise.then(onFulfilled,onRejected).then(onFulfilled,onRejected)
主要思路就是我们可以直接对返回的promise对象进行操作,比如then,传入回调,
这里的函数并不会立即执行,而是加入队列,等待未来的某个时间resolve时被触发执行。
有了以上说明,就可以来实现了
首先定义三个状态
var PENDING = undefined, FULFILLED = 1, REJECTED = 2;
然后实现Promise构造函数,此函数接受一个函数参数,函数参数接受俩个我们提供的方法resolve与reject,
供使用者在未来的某个时间里调用触发执行我们的队列,这里还要初始下当前的状态,传递的值,
以及then时保存到的队列。
大概像下面这样
var Promise = function(resolver){
if (!isFunction(resolver))
throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
if(!(this instanceof Promise)) return new Promise(resolver); var promise = this;
promise._value;
promise._reason;
promise._status = PENDING;
promise._resolves = [];
promise._rejects = []; var resolve = function(value){
//状态转换为FULFILLED
//执行then时保存到_resolves里的回调,
//如果回调有返回值,更新当前_value
}
var reject = function(reason){
//状态转换为REJECTED
//执行then时保存到_rejects里的回调,
//如果回调有返回值,更新当前_rejects
} resolver(resolve,reject);
}
有了这个,我们在实现一个then就ok了,
then里要做的就是返回一个promise供then的链式调用,
而且promise.then(onFulfilled,onRejected)时,我们要判断当前promise的状态,
如果是pending则把onFulfilled与onRejected添加到_resolves与_rejects数组里,
否则的话根据状态,直接触发回调,这里要注意的是,如果返回的是promise,我们要等到此promise被resolves时,
触发then链的下一个promise执行。
代码大概是这样
Promise.prototype.then = function(onFulfilled,onRejected){
var promise = this;
// 每次返回一个promise,保证是可thenable的
return Promise(function(resolve,reject){
function callback(value){
var ret = isFunction(onFulfilled) && onFulfilled(value) || value;
if(isThenable(ret)){
// 根据返回的promise执行的结果,触发下一个promise相应的状态
ret.then(function(value){
resolve(value);
},function(reason){
reject(reason);
});
}else{
resolve(ret);
}
}
function errback(reason){
reason = isFunction(onRejected) && onRejected(reason) || reason;
reject(reason);
}
if(promise._status === PENDING){
promise._resolves.push(callback);
promise._rejects.push(errback);
}else if(promise._status === FULFILLED){ // 状态改变后的then操作,立刻执行
callback(promise._value);
}else if(promise._status === REJECTED){
errback(promise._reason);
}
});
}
这里说明下
var isThenable = function(obj){
return obj && typeof obj['then'] == 'function';
}
也就是说返回的对象带有then方法,我们就当作promise对象
到这里我们主要的工作就完成了,其他的all,race等方法都很简单,具体可以到这里看完整的实现
https://github.com/ygm125/promise/blob/master/promise.js
下面我们来做几个例子来看下效果
var getData100 = function(){
return Promise(function(resolve,reject){
setTimeout(function(){
resolve('100ms');
},100);
});
} var getData200 = function(){
return Promise(function(resolve,reject){
setTimeout(function(){
resolve('200ms');
},200);
});
} getData100().then(function(data){
console.log(data); // 100ms
return getData200();
}).then(function(data){
console.log(data); // 200ms
return data + data;
}).then(function(data){
console.log(data) // 200ms200ms
});
当然可以直接getData100().then(getData200).then(function(val){})
then可以只传一个,接受成功的回调,也可以用catch方法,接受失败的回调,
catch是then的一个语法糖,相当于promise.then(undefined, onRejected)
也可以用all来并行执行
Promise.all([getData100(),getData200()]).then(function(value){
console.log(value) // ['100ms','200ms']
});
结果的顺序与传入的顺序相同。
我们也可以直接创建一个以obj为值的成功状态的promise,
Promise.resolve('FULFILLED').then(function(val){
console.log(val) // FULFILLED
});
实现都相当简单,看代码就懂。
这里也可以做一些好玩的,比如创建一个delay方法
Promise.prototype.delay = function(ms){
return this.then(function(val){
return Promise.delay(ms,val);
})
} Promise.delay = function(ms,val){
return Promise(function(resolve,reject){
setTimeout(function(){
resolve(val);
},ms);
})
}
我们可以每隔多少毫秒执行一些操作
Promise.delay(1000).then(function(){
// 一些操作
}).delay(1000).then(function(){
// 一些操作
})
我们也可以包装一个循环,执行多少次,每次延迟多少秒执行什么操作
<div id="say"></div>
<script type="text/javascript">
!function(a,b){var c=b,d=1,e=2,f=function(a){return"function"==typeof a},g=function(a){return"[object Array]"===Object.prototype.toString.call(a)},h=function(a){return a&&"function"==typeof a.then},i=function(a,b){var d=this;d._status===c&&setTimeout(function(){d._status=a,j.call(d,b)})},j=function(a){for(var e,c=this,f=c._status===d,g=c[f?"_resolves":"_rejects"];e=g.shift();)a=e.call(c,a)||a;c[f?"_value":"_reason"]=a,c._resolves=c._rejects=b},k=function(a){var b,g,h;if(!f(a))throw new TypeError("You must pass a resolver function as the first argument to the promise constructor");return this instanceof k?(b=this,b._value,b._reason,b._status=c,b._resolves=[],b._rejects=[],g=function(a){i.apply(b,[d].concat([a]))},h=function(a){i.apply(b,[e].concat([a]))},a(g,h),void 0):new k(a)};k.prototype.then=function(a,b){var g=this;return k(function(i,j){function k(b){var c=f(a)&&a(b)||b;h(c)?c.then(function(a){i(a)},function(a){j(a)}):i(c)}function l(a){a=f(b)&&b(a)||a,j(a)}g._status===c?(g._resolves.push(k),g._rejects.push(l)):g._status===d?k(g._value):g._status===e&&l(g._reason)})},k.prototype.catch=function(a){return this.then(b,a)},k.prototype.delay=function(a){return this.then(function(b){return k.delay(a,b)})},k.delay=function(a,b){return k(function(c){setTimeout(function(){c(b)},a)})},k.resolve=function(a){return k(function(b){b(a)})},k.reject=function(a){return k(function(b,c){c(a)})},k.all=function(a){if(!g(a))throw new TypeError("You must pass an array to all.");return k(function(b,c){function g(a){return function(b){i(a,b)}}function h(a){c(a)}function i(a,c){e[a]=c,a==f-1&&b(e)}for(var d=0,e=[],f=a.length;f>d;d++)a[d].then(g(d),h)})},k.race=function(a){if(!g(a))throw new TypeError("You must pass an array to race.");return k(function(b,c){function f(a){b(a)}function g(a){c(a)}for(var d=0,e=a.length;e>d;d++)a[d].then(f,g)})},a.Promise=k}(window);
var words = '你好,你是谁?',
len = 0;
say = document.getElementById("say");
function count(num,ms,cb){
var pro = Promise.resolve();
for (var i = 0; i < num; i++) {
pro = pro.delay(ms).then(function(v){
return cb(v);
});
};
}
count(words.length,800,function(){
var w = words.substr(0,++len);
say.innerHTML= w;
})
</script>
var len = 0,
words = '你好,你是谁?'; function count(num,ms,cb){
var pro = Promise.resolve();
for (var i = 0; i < num; i++) {
pro = pro.delay(ms).then(function(v){
return cb(v);
});
};
} count(words.length,800,function(){
var w = words.substr(0,++len);
console.log(w);
})
更多的东西等你来实现~
教你一步一步实现一个Promise的更多相关文章
- 通过Dapr实现一个简单的基于.net的微服务电商系统(四)——一步一步教你如何撸Dapr之订阅发布
之前的章节我们介绍了如何通过dapr发起一个服务调用,相信看过前几章的小伙伴已经对dapr有一个基本的了解了,今天我们来聊一聊dapr的另外一个功能--订阅发布 目录:一.通过Dapr实现一个简单的基 ...
- 通过Dapr实现一个简单的基于.net的微服务电商系统(三)——一步一步教你如何撸Dapr
目录:一.通过Dapr实现一个简单的基于.net的微服务电商系统 二.通过Dapr实现一个简单的基于.net的微服务电商系统(二)--通讯框架讲解 三.通过Dapr实现一个简单的基于.net的微服务电 ...
- 通过Dapr实现一个简单的基于.net的微服务电商系统(五)——一步一步教你如何撸Dapr之状态管理
状态管理和上一章的订阅发布都算是Dapr相较于其他服务网格框架来讲提供的比较特异性的内容,今天我们来讲讲状态管理. 目录:一.通过Dapr实现一个简单的基于.net的微服务电商系统 二.通过Dapr实 ...
- 通过Dapr实现一个简单的基于.net的微服务电商系统(六)——一步一步教你如何撸Dapr之Actor服务
我个人认为Actor应该是Dapr里比较重头的部分也是Dapr一直在讲的所谓"stateful applications"真正具体的一个实现(个人认为),上一章讲到有状态服务可能很 ...
- 通过Dapr实现一个简单的基于.net的微服务电商系统(七)——一步一步教你如何撸Dapr之服务限流
在一般的互联网应用中限流是一个比较常见的场景,也有很多常见的方式可以实现对应用的限流比如通过令牌桶通过滑动窗口等等方式都可以实现,也可以在整个请求流程中进行限流比如客户端限流就是在客户端通过随机数直接 ...
- 通过Dapr实现一个简单的基于.net的微服务电商系统(八)——一步一步教你如何撸Dapr之链路追踪
Dapr提供了一些开箱即用的分布式链路追踪解决方案,今天我们来讲一讲如何通过dapr的configuration来实现非侵入式链路追踪的 目录:一.通过Dapr实现一个简单的基于.net的微服务电商系 ...
- 通过Dapr实现一个简单的基于.net的微服务电商系统(九)——一步一步教你如何撸Dapr之OAuth2授权
Oauth2授权,熟悉微信开发的同学对这个东西应该不陌生吧.当我们的应用系统需要集成第三方授权时一般都会做oauth集成,今天就来看看在Dapr的语境下我们如何仅通过配置无需修改应用程序的方式让第三方 ...
- 通过Dapr实现一个简单的基于.net的微服务电商系统(十)——一步一步教你如何撸Dapr之绑定
如果说Actor是dapr有状态服务的内部体现的话,那绑定应该是dapr对serverless这部分的体现了.我们可以通过绑定极大的扩展应用的能力,甚至未来会成为serverless的基础.最开始接触 ...
- 通过Dapr实现一个简单的基于.net的微服务电商系统(十一)——一步一步教你如何撸Dapr之自动扩/缩容
上一篇我们讲到了dapr提供的bindings,通过绑定可以让我们的程序轻装上阵,在极端情况下几乎不需要集成任何sdk,仅需要通过httpclient+text.json即可完成对外部组件的调用,这样 ...
随机推荐
- Helloworld -SilverN
/*Hello World*/ #include<iostream> #include<cstdio> #include<cstring> using namesp ...
- 【温故而知新-Javascript】使用数组
Javascript 数组的工作方式与大多数编程语言的数组类似. <!DOCTYPE html> <html lang="en"> <head> ...
- 使用Burpsuite破解Webshell密码
Burp Suite 是用于攻击web 应用程序的集成平台.它包含了许多工具,并为这些工具设计了许多接口,以促进加快攻击应用程序的过程.所有的工具都共享一个能处理并显示HTTP 消息,持久性,认证 ...
- [3D跑酷] DataManager
DataManager管理游戏中数据,当然这个类中大部分的属性和方法都是Public 函数列表
- find命令错误提示路径必须在表达式之前
在某些版本的linux下,通过find查找当前目录下所有后缀名jpg的文件,命令为find ./ -iname *.jpg 会出现“find: 路径必须在表达式之前”的错误提示.解决的方法有两种 a. ...
- 程序清单 8-8 exec函数实例,a.out是程序8-9产生的可执行程序
/* ============================================================================ Name : test.c Author ...
- JQuery demo
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <title> ...
- 23Mybatis_根据订单商品数据模型的练习对resultMap和resulttype的总结
resultType: 作用: 将查询结果按照sql列名pojo属性名一致性映射到pojo中. 场合: 常见一些明细记录的展示,比如用户购买商品明细,将关联查询信息全部展示在页面时,此时可直接使用re ...
- ssh相关操作
连接:ssh username@ip 拷贝ssh客户端文件到ssh服务器: cp 文件名 username@ip:文件名 拷贝ssh服务器文件夹到ssh客户端: scp -r wanglianghe@ ...
- Django基础 - Debug设置为False后静态文件获取404
当设置setting.py文件当中的DEBUG=FALSE后,Django会默认使用Web Server的静态文件处理,故若没设置好Web Server对静态文件的处理的话,会出现访问静态文件404的 ...