教你一步一步实现一个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的使用大致如下

?





1

2

3

4

5

6

7

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,布布扣,bubuko.com

时间: 2024-10-07 05:02:41

教你一步一步实现一个Promise的相关文章

一步一步教你用Swift开发俄罗斯方块:No.1 建立你的第一个Swift游戏工程

原文地址:https://www.bloc.io/tutorials/swiftris-build-your-first-ios-game-with-swift#!/chapters/677 好了,我们正式开始我们的swift游戏开发! 首先,需要新建一个工程,熟悉iOS开发的童鞋应该对这个步骤不会陌生.我们还是一步一步来吧: 这里有两种途径建立全新的工程,你可以: 如果你的Mac没有运行Xcode,请打开它,然后在欢迎页面点击Create a new Xcode project 这里我还是要重

教你一步一步实现图标无缝变形切换

我的简书同步发布:教你一步一步实现图标无缝变形切换 ?欢迎打赏? 转载请注明出处:[huachao1001的专栏:http://blog.csdn.net/huachao1001] 本来这篇文章几天前就应该写好并发布出来的,由于要写小论文,被导师劈头盖脸的骂了几天,一直在搞论文,耽误了博文的编写.今天终于把小论文给投出去了,终于可以好好写博客了! 在上一篇文章<酷炫的Activity切换动画,打造更好的用户体验 >中,我们感受到了过渡切换动画带来的不一样的用户体验.如何你还意犹未尽,那么今天我

一步一步教你如何重装笔记本电脑系统?

本文标签:  电脑技巧 重装笔记本电脑系统 重装系统 重装dell联想宏碁电脑系统 原文地址:http://whosmall.com/?post=461 不知不觉中,已在程序猿这个职业中疯狂熬过去了3年时间,这3年虽然苹果技术天天更新,天天进步,但是如计算机常识方面却不忍心看它还是原地踏步! 从事编程时间久了,每次回家的时候,免不了会有朋友说起听说你从事计算机工作的吧,是啊,那帮我装个系统吧,最近电脑卡的要命.我家网线坏了,帮我连下网线吧!更有甚者,说我刚才误删某某重要文件,帮我恢复下吧! 你要

【Unity3D实战】零基础一步一步教你制作跑酷类游戏(填坑完整版)

在两个月前曾写了一篇<[Unity3D实战]零基础一步一步教你制作跑酷类游戏(1)>,里面一步一步演示了制作跑酷类游戏,然而由于时间原因,只写到了让角色往前移动为止.这个坑一直没有时间去填,(虽然也没多少人看啦),今天刚好有时间完成了一个跑酷类游戏的Demo.放上来给有兴趣的朋友看看. Demo源码及对应素材下载:链接:http://pan.baidu.com/s/1i4QkkuD 密码:p04w 游戏简要说明 游戏类型:跑酷类游戏(Demo,非完整游戏) 操作方式:左右方向键(可自己移植到手

Ace教你一步一步做Android新闻客户端(一)

复制粘贴了那么多博文很不好意思没点自己原创的也说不出去,现在写一篇一步一步教你做安卓新闻客户端,借此机会也是让自己把相关的技术再复习一遍,大神莫笑,专门做给新手看. 手里存了两篇,一个包括软件视图 和新手引导 软件侧滑菜单 滑动主页的GUI篇 一个内容解析篇. 代码里有很详细的注释 所以直接放代码了 有不会的站内信或者评论我会及时回复. MainActivity XML :只有一个ListView布局 <?xml version="1.0" encoding="utf-

C#WPF 语音开发教程 源代码下载 csdn tts(text to sound) 一步一步 教你制作语音软件 附图和源代码

C#WPF  语音开发教程  一步一步 教你制作语音软件 附图和源代码 效果展示 一 项目准备 1.vs2012开发平台 2.微软的语音软件库 下载:http://download.csdn.net/detail/wyx100/8431269 (含实例项目源代码) 二.开发目标 制作一个语音软件,可以朗读文字: 多个语音库:男音和女音.支持英文和中文朗读: 支持选择播放设备 支持朗读语速选择 支持音量选择 三 开发过程 1.新建WpfSpeechDemo工程 文件(vs开发平台左上角)----新

一步一步教你用 Vue.js + Vuex 制作专门收藏微信公众号的 app

一步一步教你用 Vue.js + Vuex 制作专门收藏微信公众号的 app 转载 作者:jrainlau 链接:https://segmentfault.com/a/1190000005844155 项目地址:https://github.com/jrainlau/wechat-subscriptor 下载&运行 git clone git@github.com:jrainlau/wechat-subscriptor.git cd wechat-subscriptor && np

【好文翻译】一步一步教你使用Spire.Doc转换Word文档格式

背景: 本文试图证明和审查Spire.Doc的格式转换能力.很长的一段时间里,为了操作文档,开发人员不得不在服务器上安装Office软件.首先,这是一个很糟糕的设计和实践.第二,微软从没打算把Office作为一个服务器组件,它也用来在服务器端解释和操作文档的.于是乎,产生了类似Spire.Doc这样的类库.当我们讨论这个问题时,值得一提的是 Office Open Xml. Office Open XML (也有非正式地称呼为 OOXML 或OpenXML) 是一种压缩的, 基于XML的文件格式

一步一步教你搭建基于docker的MongoDB复制集群环境

一步一步教你搭建基于docker的MongoDB复制集群环境 1.安装docker 2.创建MongoDB的Image 3.搭建MongoDB的集群 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中. 1.ubuntu14.04安装docker 参考文档 http://docs.docker.com/installation/ubuntulinux/ 参考文档 http://docs.docker.com/mac/started/ pc@pc-Th