【转】实现一个自己的promise
转, 原文:http://blog.csdn.net/yibingxiong1/article/details/68075416
-------------------------------------------
这是小弟的一篇开篇小作,如有不当之处,请各位道友批评指正。本文将探讨Promise的实现。
一、ES6中的Promise
1、简介
据说js很早就实现了Promise,我是不知道的,我第一次接触Promise就是在ES6中。Promise就是规定在未来达到某个状态时应该采取某种行动,而这种未来的状态是不确定的。阮一峰说Promise对象用来传递异步消息,代表了某个未来才会知道结果的事件,并为这个事件提供统一的API,供进一步处理。
Promise和事件有本质区别:Promise是用一次就扔,而事件可以被多次触发;Promise必定会产生一个信号,要么resolved(fulfilled),要么rejected,而事件可能不被触发。
2、基本用法
var p = new Promise(function(resolve){
setTimeout(function(){
resolve(111);
},1000)
});
p.then(function(value){
console.log("承诺解决了,拿到的数据为:"+value);
});
上面创建了一个promise,1秒后解决,然后用then方法添加了状态改变的回调函数。then方法中可以指定两个函数,第一个位承诺是变成resolved状态的回调函数,第二个是承诺变为rejected状态的回调函数(可省略)。也可以在catch方法中指定承诺变为rejected的函数。如下:
p.catch(function(error){
console.log("rejected callback");
})
3、Promise.all()的用法
它接受一个promise对象数组为参数,当数组中的每一个promise都变成resolved状态时,整个才算为resolved,数组中promise的返回值将组成一个数组传递给Promise.all()返回的promise的回调函数。数组中的只要有一个为rejected整个为rejected;
var p1 = new Promise(function(resolve){
resolve("p1 resolved");
});
var p2 = new Promise(function(resolve){
resolve('p2 resolved');
});
var p3 = new Promise(function(resolve){
resolve("p3 resolved");
});
var p = Promise.all([p1,p2,p3]);//这里得到了一个新的promise
p.then(function(value){
console.log("p resolved, 得到的参数为:"+value);
})
.catch(function(error){
console.log("p rejected,"+error);
});
//output:p resolved, 得到的参数为:p1 resolved,p2 resolved,p3 resolved
将p2改为rejected
var p1 = new Promise(function(resolve){
resolve("p1 resolved");
});
var p2 = new Promise(function(resolve,reject){
reject('p2 rejected');
});
var p3 = new Promise(function(resolve){
resolve("p3 resolved");
});
p = Promise.all([p1,p2,p3]);
p.then(function(value){
console.log("p resolved, 得到的参数为:"+value);
})
.catch(function(error){
console.log("p rejected,"+error);
});
//output:p rejected,p2 rejected
4、Promise.race()的用法
它和Promise.all()的参数相同,它返回的promise的状态随着promise数组中第一个状态发生改变的promise的状态而改变。
var p1 = new Promise(function(resolve){
resolve("p1 resolved");
});
var p2 = new Promise(function(resolve,reject){
reject('p2 rejected');
});
var p3 = new Promise(function(resolve){
resolve("p3 resolved");
});
p = Promise.race([p1,p2,p3]);
p.then(function(value){
console.log("p resolved, 得到的参数为:"+value);
})
.catch(function(error){
console.log("p rejected,"+error);
});
//output:p resolved, 得到的参数为:p1 resolved
我们发现虽然p2是rejected,但是p是resolved,因为第一个状态变化的是p1,而p1是resolved。如果p1是rejected,那么p一定是rejected。
上面就是ES6原生支持的Promise,那么我们该如何实现一个类似的自己的Promise呢?都原生支持了为什么还要自己实现呢?第一它是一种乐趣。第二它可以帮助我们更好的认识发布订阅模式。下面开始正式战斗!(有木有很激动,终于要开始了)。
二、实现自己的Promise
1、基础实现
function Promise(fn) {
var value = null,
deferreds = [];
this.then = function (onFulfilled) {
deferreds.push(onFulfilled);
};
function resolve(value) {
deferreds.forEach(function (deferred) {
deferred(value);
});
}
fn(resolve);
}
这个构造函数传进来一个function,声明了俩个内部变量,value传到其内部方法resolve,defferreds存储回调函数,每次调用实例的then方法会让then中的函数入队,其内部方法resolve接受一个参数,它遍历了defferreds队列,并执行其中的方法。这个内部方法被传递给了构造函数的参数fn。结合ES6中Promise的基本用法应该不难理解这段代码。其then方法相当于是一个订阅过程,resolve方法相当于一个发布过程。
var mypromise = new Promise(function(resolve){
setTimeout(function(){
resolve("qqq");
},1000)
});
mypromise.then(function(value){
console.log(value); //qqq
})
2、问题修复
上述Promise的问题是,如果传进去的不是一个异步函数,那么resolve方法会先执行,此时还没有调用then,也就是说还没有人订阅,defferreds队列还是空的,不合预期。改进如下:
function Promise(fn) {
var value = null,
deferreds = [];
this.then = function (onFulfilled) {
deferreds.push(onFulfilled);
};
function resolve(value) {
//这里将resolve放到了栈底,所以then会先执行(如果有then的话)
setTimeout(function () {
deferreds.forEach(function (deferred) {
deferred(value);
});
}, 0);
}
fn(resolve);
}
3、考虑一种情况:如果回调函数注册的很晚会怎么样
var mypromise = new Promise(function(resolve){
resolve("qqq");
});
setTimeout(function(){
mypromise.then(function(value){
console.log(value);
})
},1000);
结果是啥也没做,为什么呢?因为当我们注册回调的时候resolve已经执行了。那可咋整呢?解决方法就是记住Promise的状态。请看代码:
function Promise(fn) {
var state = 'pending',
value = null,
deferreds = [];
this.then = function (onFulfilled) {
if (state === 'pending') {
deferreds.push(onFulfilled);
return this;
}
onFulfilled(value);
return this;
};
function resolve(newValue) {
value = newValue;
state = 'fulfilled';
setTimeout(function () {
deferreds.forEach(function (deferred) {
deferred(value);
});
}, 0);
}
fn(resolve);
}
就是这么简单,我们为Promise增加了一个内部变量state保存其状态,初始为pending(等待的意思),当resolve时,改变其状态为fulfilled,调then的时候做个判断,如果是pending说明resolve方法还没执行,那么我们将回调函数加到队列等待resolve即可,如果是fulfilled,说明resolve已经执行,那么我们直接执行新加入的回调函数。至于那个return this,如果你用过jquery就知道了。
4、Promise链
我们考虑这样的情况
var mypromise = new Promise(function(resolve){
resolve("first promise");
});
mypromise.then(function(value){
console.log(value);
return new Promise(function(resolve){
resolve("second promise");
})
})
.then(function(v){
console.log(v);
})
//结果:first promise
// first promise
//为啥是两个first promise,这个是必然的,好好想想。第一个then返回的是前一个promise,而不是新创建的promise。
下面做一件有难度的是,我们来实现promise链,我看了几个小时才搞明白。不要怕,打起精神来,我们开始。
this.then = function (onFulfilled) {
return new Promise(function (resolve) {
handle({
onFulfilled: onFulfilled || null,
resolve: resolve
});
});
};
function handle(deferred) {
if (state === 'pending') {
deferreds.push(deferred);
return;
}
var ret = deferred.onFulfilled(value);
deferred.resolve(ret);
}
function resolve(newValue) {
if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
var then = newValue.then;
if (typeof then === 'function') {
then.call(newValue, resolve);
return;
}
}
state = 'fulfilled';
value = newValue;
setTimeout(function () {
deferreds.forEach(function (deferred) {
handle(deferred);
});
}, 0);
}
then方法中返回了一个新的promise实例,在新实例中调了一个内部方法,传进去一个回调函数和一个resolve方法构成的对象;内部方法判断如果当前promise没有调resolve的话,将传入的对象入队,否则的话直接调传入的回调函数,将回调函的返回值交给resolove方法。想一下,我们的回调函数会返回什么值,可能是一个promise对象也可能是字符串或者undefined。如果说返回了一个对象,证明用户又返回了一个promise,此时我们将用户返回的promise的then拿到,然后调这个then,如果不是个对象或者函数,证明用户没有返回新的promise,此时直接resolve。
5、拒绝状态
前面讲了resolve,reject就是手到禽来了。
function Promise(fn) {
var state = 'pending',
value = null,
deferreds = [];
this.then = function (onFulfilled, onRejected) {
return new Promise(function (resolve, reject) {
handle({
onFulfilled: onFulfilled || null,
onRejected: onRejected || null,
resolve: resolve,
reject: reject
});
});
};
function handle(deferred) {
if (state === 'pending') {
deferreds.push(deferred);
return;
}
var cb = state === 'fulfilled' ? deferred.onFulfilled : deferred.onRejected,
ret;
if (cb === null) {
cb = state === 'fulfilled' ? deferred.resolve : deferred.reject;
cb(value);
return;
}
ret = cb(value);
deferred.resolve(ret);
}
function resolve(newValue) {
if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
var then = newValue.then;
if (typeof then === 'function') {
then.call(newValue, resolve, reject);
return;
}
}
state = 'fulfilled';
value = newValue;
finale();
}
function reject(reason) {
state = 'rejected';
value = reason;
finale();
}
function finale() {
setTimeout(function () {
deferreds.forEach(function (deferred) {
handle(deferred);
});
}, 0);
}
fn(resolve, reject);
}
6、处理resolve和reject的回调函数异常
//改造了内部函数handle
function handle(deferred) {
if (state === 'pending') {
deferreds.push(deferred);
return;
}
var cb = state === 'fulfilled' ? deferred.onFulfilled : deferred.onRejected,
ret;
if (cb === null) {
cb = state === 'fulfilled' ? deferred.resolve : deferred.reject;
cb(value);
return;
}
try {
ret = cb(value);
deferred.resolve(ret);
} catch (e) {
deferred.reject(e);
}
}
真是无语,我已经写完了,Promise.all()和Promise.race()的实现和总结都写好了。已为添加参考文献是那个引用,按了快捷键Ctrl+Q,结果退出了浏览器,气死了。不写了,大家可以参考文章最后的参考文献,不过那个race方法的实现可能还有点问题,返回的promise的状态不是跟第一个数组中状态发生改变的promise的状态一致,而是最后一个。
三、总结
第一次写博客,突然那些写了那么多优秀文章的作者表示由衷佩服。这个Promise的实现确实是非常巧妙的,如果真的要我独自实现,恐怕还需要一些时日。不管怎样,终于完成了我的开篇之作。写作过程中对Promise的实现有了进一步认识。最后希望自己能够坚持写博客,在写作中学习。
参考文献:
- 手把手教你实现一个完整的 Promise
- 剖析 Promise 之基础篇
- ES6标准入门(阮一峰)
【转】实现一个自己的promise的更多相关文章
- 手把手教你实现一个完整的 Promise
用过 Promise,但是总是有点似懂非懂的感觉,也看过很多文章,还是搞不懂 Promise的 实现原理,后面自己边看文章,边调试代码,终于慢慢的有感觉了,下面就按自己的理解来实现一个 Promise ...
- 一个简单的Promise 实现
用了这么长时间的promise,也看了很多关于promise 的文章博客,对promise 算是些了解.但是要更深的理解promise,最好的办法还是自己实现一个. 我大概清楚promise 是对异步 ...
- 实现一个自己的promise
这是小弟的一篇开篇小作,如有不当之处,请各位道友批评指正.本文将探讨Promise的实现. 一.ES6中的Promise 1.简介 据说js很早就实现了Promise,我是不知道的,我第一次接触Pro ...
- 【JavaScript进阶】深入理解JavaScript中ES6的Promise的作用并实现一个自己的Promise
1.Promise的基本使用 // 需求分析: 封装一个方法用于读取文件路径,返回文件内容 const fs = require('fs'); const path = require('path') ...
- 从源码角度实现一个自己的Promise
原文链接:https://geniuspeng.github.io/2017/12/14/my-promise/ 关于Promise的概念以及意义就不在这里介绍了,最近看到了一些实现Promise的核 ...
- 彻底理解Promise对象——用es5语法实现一个自己的Promise(上篇)
本文同步自我的个人博客: http://mly-zju.github.io/ 众所周知javascript语言的一大特色就是异步,这既是它的优点,同时在某些情况下也带来了一些的问题.最大的问题之一,就 ...
- 如何在外部终止一个pengding的promise对象
今天在整理前段时间做过的项目,发现之前在集成web环信的时候遇到过一个奇怪的需求:需要终止一个正在进行等待返回的promise,或者阻止其调用resolve和reject.(具体为何会有这种需求我也不 ...
- 如何用原生JS实现一个简单的promise
我又又又回来了,最近真是累的跟狗一样,急需一个大保健回复一下子精力 我现在是一边喝着红牛一边写着博客,好了好了,不扯了,回归整体好吧 先简单来说一下啥是promise吧 它是什么?Promise是一个 ...
- 如何实现一个串行promise
异步执行任务A.B.C,...... 1.使用数组的reduce方法,reduce里有四个参数,pre,next,index,arr, 2.如果then方法里返回的是一个promise对象,那么执行下 ...
随机推荐
- Jvisualvm--JAVA性能分析工具
JDK自带的JAVA性能分析工具.它已经在你的JDK bin目录里了,只要你使用的是JDK1.6 Update7之后的版本.点击一下jvisualvm.exe图标它就可以运行了. 这里是VisualV ...
- h5 移动端 监听软键盘弹起、收起
前面一篇博客 h5 安卓 键盘弹起界面适配 修改webview高度提到了在adnroid中如何监听软键盘的弹起与收起,是利用的窗口的高度发生变化window.onresize事件来做突破点的,但是io ...
- 【JavaScript从入门到精通】第三课
第三课 初探JavaScript魅力-03 函数传参 上节课的时候我们已经讲了什么是函数,实际上,函数在功能上就类似于css的class一样,将一段代码包裹起来使用.为了让函数的功能更加的丰富和实用, ...
- java.lang.NoClassDefFoundError: org.springframework.beans.FatalBeanException
在进行Spring和Hibernate整合的时候遇到了这个问题, 问题描述如下 问题原因? Spring的Bean的XML配置文件存在错误 解决方法: 正确的配置XML文件,例如下面的代码 < ...
- ORA-39126: Worker unexpected fatal error in KUPW$WORKER.PUT_DDLS
末尾加上EXCLUDE=STATISTICS /home/opt/oracle/11g/bin/expdp user/password directory=backup2 network_link=d ...
- 启动web项目卡在Initializing Spring root WebApplicationContext不动
这几天在和同学一起做一个电教器材管理系统的Web项目,用SVN互通,在此记录下经常遇到的bug. Bug: 启动项目一直卡在Initializing Spring root WebApplicatio ...
- C++11新特性之final override标识符
final: final修饰符可用于修饰类,放在类名后面,被final修饰符修饰的类不能被继承.示例代码: // 正确的示范 #include <iostream> class A { p ...
- spring Boot 不认Mapper.xml
很久以前的笔记了,大约就是用Generatro工具自动生成代码的时候,springboot找不到mapper.xml 之前,由于用mybatis-generator自动生成了entity,dao,ma ...
- 微信小程序 video组件 不随页面滚动
1.页面初始化(滚动前)时,video所在位置 2.页面滚动后,video视频组件所在位置 看了别人家的小程序并不会出现这种状况.最后检查发现,是页面包裹层设置了 height:100% 导致的 顺便 ...
- 树莓派 -- i2c学习 续(1) DeviceTree Overlay实例化rtc
上文中讨论了通过sysfs来实例化i2c设备 (rtc ds3231) https://blog.csdn.net/feiwatson/article/details/81048616 本文继续看看如 ...