这是小弟的一篇开篇小作,如有不当之处,请各位道友批评指正。本文将探讨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的更多相关文章

  1. 手把手教你实现一个完整的 Promise

    用过 Promise,但是总是有点似懂非懂的感觉,也看过很多文章,还是搞不懂 Promise的 实现原理,后面自己边看文章,边调试代码,终于慢慢的有感觉了,下面就按自己的理解来实现一个 Promise ...

  2. 一个简单的Promise 实现

    用了这么长时间的promise,也看了很多关于promise 的文章博客,对promise 算是些了解.但是要更深的理解promise,最好的办法还是自己实现一个. 我大概清楚promise 是对异步 ...

  3. 【转】实现一个自己的promise

    转, 原文:http://blog.csdn.net/yibingxiong1/article/details/68075416------------------------------------ ...

  4. 【JavaScript进阶】深入理解JavaScript中ES6的Promise的作用并实现一个自己的Promise

    1.Promise的基本使用 // 需求分析: 封装一个方法用于读取文件路径,返回文件内容 const fs = require('fs'); const path = require('path') ...

  5. 从源码角度实现一个自己的Promise

    原文链接:https://geniuspeng.github.io/2017/12/14/my-promise/ 关于Promise的概念以及意义就不在这里介绍了,最近看到了一些实现Promise的核 ...

  6. 彻底理解Promise对象——用es5语法实现一个自己的Promise(上篇)

    本文同步自我的个人博客: http://mly-zju.github.io/ 众所周知javascript语言的一大特色就是异步,这既是它的优点,同时在某些情况下也带来了一些的问题.最大的问题之一,就 ...

  7. 如何在外部终止一个pengding的promise对象

    今天在整理前段时间做过的项目,发现之前在集成web环信的时候遇到过一个奇怪的需求:需要终止一个正在进行等待返回的promise,或者阻止其调用resolve和reject.(具体为何会有这种需求我也不 ...

  8. 如何用原生JS实现一个简单的promise

    我又又又回来了,最近真是累的跟狗一样,急需一个大保健回复一下子精力 我现在是一边喝着红牛一边写着博客,好了好了,不扯了,回归整体好吧 先简单来说一下啥是promise吧 它是什么?Promise是一个 ...

  9. 如何实现一个串行promise

    异步执行任务A.B.C,...... 1.使用数组的reduce方法,reduce里有四个参数,pre,next,index,arr, 2.如果then方法里返回的是一个promise对象,那么执行下 ...

随机推荐

  1. HTML5拖放加入购物车

    H5拖放事件巩固实例: 1.简单布局一下,商品信息放入一个ul中:div为购物车,后续会创建元素 <ul> <li draggable="true"> &l ...

  2. CSS1,CSS2选择器详解

    第一.CSS1选择器: 1.元素选择器(也叫标签选择器,是最基本的选择器) <style> html{background-color: red;} div{background-colo ...

  3. 批量删除的PHP

    第一个页面shanchu.php <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "h ...

  4. rpm的用法

    rpm是由红帽公司开发的软件包管理方式,使用rpm我们可以方便的进行软件的安装.查询.卸载.升级等工作.但是rpm软件包之间的依赖性问题往往会很繁琐,尤其是软件由多个rpm包组成时. yum基於RPM ...

  5. Vue学习之路---No.1(分享心得,欢迎批评指正)

    首先为了打消大家对Vue.js存在的顾虑,先通过大家所熟知的JQ作为对比. 都知道JQ的语法相对简单.清楚.使用方便.功能齐全: 那么Vue.js呢,同样的,Vue.js与JQ在很多地方都是相同之处, ...

  6. 《深入理解Java虚拟机》学习笔记之内存分配

    JVM在执行Java程序的过程中会把它所管理的内存划分若干个不同的数据区域,如下图: 大致可以分为两类:线程私有区域和线程共享区域. 线程私有区域 程序计数器(Program Counter Regi ...

  7. 3433: [Usaco2014 Jan]Recording the Moolympics

    3433: [Usaco2014 Jan]Recording the Moolympics Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 137  S ...

  8. SEO-友情链接注意事项

    为什么要专门给友链一个区域呢?由此就可以想象到友情链接对一个网站有多重要前期,网站没有权重的时候,跟别人换友链,人家基本是不会换的因为你网站没权重,加了友链他也获取不到权重,对网站没有多少好处一般我们 ...

  9. C++—引用的作用

    引入 C语言中函数有两种传参的方式: 传值和传址.以传值方式, 在函数调用过程中会生成一份临时变量用形参代替, 最终把实参的值传递给新分配的临时变量即形参. 它的优点是避免了函数调用的副作用, 确无法 ...

  10. css布局与盒子模型

    一.    盒子模型 注: 1.红色为border; 2.背景应用于内容.内边距.边框组成的区域: 3.Width和height指的是内容区域的高度和宽度. 边框属性: 1.  padding属性:( ...