分步理解 Promise 的实现
一个 Promise 的运用:
var firstPromise = new Promise(function(resolve,reject){
setTimeout(function(){
var result = Math.random() <= 0.5 ? 1:0;
if(result){
resolve('resolved');
}else{
reject('rejected')
}
},1000)
}) var secondPromise = new Promise(function(resolve,reject){
setTimeout(function(){
var result = Math.random() <= 0.5 ? 1:0;
if(result){
resolve('resolved');
}else{
reject('rejected')
}
},2000)
}) firstPromise.then(function(value){
console.log(value);
return secondPromise;
},function(reason){
console.log(reason);
return secondPromise;
}).then(function(value){
console.log(value);
},function(reason){
console.log(reason);
}) // 1s后随机输出结果 resolved 或者 rejected
// 再1s后随机输出结果 resolved 或者 rejected
效果如上,在一个 promise 被完成/被拒绝时执行对应的回调取到异步结果。
同时,以上代码使用 promise 避免了回调地狱,规范了回调操作。
接下来,把 promise 拆成几块,学习一下怎么样的实现过程。
步骤一、Promise 构造函数
创建 promise 对象的构造函数,是创造 promise 的工厂。
基础要求:Promise 函数仅产生一个对象,避免大量变量的污染,将该藏好的对象/值藏好,该暴露的暴露;Promise 接收一个函数作为参数,且该函数在构造 promise 对象时被执行;Promise 必须有个 .then 方法(后续方法可自行扩展)。
function Promise(fn){
this.then = function(){ };
}
步骤二、初始化过程,处理参数fn
Promise 构造函数参数 fn 中传入 resolve/reject;Promise 初始化的时候执行 fn 并在 promise 得到最终结果后执行传入的 resolve/reject ;resolve/reject 函数中执行 promise 中指定的完成/拒绝时回调函数,并以最终结果作为参数。
function Promise(fn){ // 完成时
function resolve(value) {
console.log('value ',value);
} // 拒绝时
function reject(reason) {
console.log('reason ',reason);
} // 执行传入的fn
function init(fn, onResolved, onRejected) {
try {
fn(function (value) {
onResolved(value);
}, function (reason) {
onRejected(reason);
})
} catch (err) {
onRejected(err);
}
} init(fn, resolve, reject); this.then = function(){ };
} var promise = new Promise(function(resolve,reject){
setTimeout(function(){
var result = Math.random() <= 0.5 ? 1:0;
if(result){
resolve('resolved')
}else{
reject('rejected')
}
},1000)
}) // 1s后随机输出 value resolved 或者 reason rejected
步骤三、.then 里的处理流程
在promise中, .then 将传入的 resolvedHandle 和 rejectedHandle 函数存入 promise 的 handlers 中作为回调列表中的一项,在需要的时候(Promise被完成的时候)携带最终结果执行。
首先,假设有个异步操作,而且已经知道回调函数是什么,代码如下:
var resolvedHandle = function(res){ console.log(res) };
var rejectedHandle = function(err){ console.log(err) }; setTimeout(function(){
var result = Math.random() <= 0.5 ? 1:0;
if(result){
resolvedHandle('resolved');
}else{
rejectedHandle('rejected');
}
},1000) // 1s后输出 resolved 或者 rejected
而对于 promise 而言,回调函数是在 .then 中传入并且在 promise 中给定义的,并且为了实现链式的操作, .then 中必须有返回一个对象,且对象须是一个携带 .then 方法的对象或函数或为一个 promise ,才足以继续执行.then。
// fn 作为初始化Promise时传入的函数,应该被立即执行并取出其中的调用
function Promise(fn) { var $resolveHandle = function (res) { };
var $rejectHandle = function (err) { }; // 执行Promise被完成时函数
function resolve(value) {
try {
var then = getThen(value);
if (then) {
init(then.bind(value), resolve, reject);
return;
};
fulfill(value);
} catch (err) {
reject(err);
}
} // 完成时
function fulfill(value) {
$resolveHandle(value);
$resolveHandle = null;
} // 拒绝时
function reject(reason) {
$rejectHandle(reason);
$rejectHandle = null;
} // 执行传入的fn并执行回调
function init(fn, onResolved, onRejected) {
try {
fn(function (value) {
onResolved(value);
}, function (reason) {
onRejected(reason);
})
} catch (err) {
onRejected(err);
}
} init(fn, resolve, reject); function getThen(value) {
var t = typeof value;
if (value && (t === 'object' || t === 'function')) {
var then = value.then;
if (typeof then === 'function') {
return then;
}
}
return null;
}; this.then = function (resolveHandle, rejectHandle) {
return new Promise(function (resolve, reject) {
$resolveHandle = function (result) {
resolve(resolveHandle(result));
}
$rejectHandle = function (reason) {
if(rejectHandle){
resolve(rejectHandle(reason));
}else{
reject(reason)
}
}
})
}
} var firstPromise = new Promise(function (resolve, reject) {
setTimeout(function () {
var result = Math.random() <= 0.5 ? 1 : 0;
if (result) {
resolve('resolved');
} else {
reject('rejected');
}
}, 1000);
}) var secondPromise = new Promise(function (resolve, reject) {
setTimeout(function () {
var result = Math.random() <= 0.5 ? 1 : 0;
if (result) {
resolve('resolved 2');
} else {
reject('rejected 2');
}
}, 2000);
}) firstPromise.then(function (res) {
console.log('res ', res);
return secondPromise;
}).then(function (res) {
console.log('res 2 ', res);
}, function (err) {
console.log('rej 2 ', err);
})
// 1s后随机输出 res resolved 或者 rej rejected
// 又1s后输出 res 2 resolved 2 或者 rej 2 rejected 2 或者 rej 2 rejected
至此,上面的代码基本算是满足了一个 promise 的实现思路,但离正规军 promise 实现还存在一段距离
o(╥﹏╥)o...接下去学习吧。
步骤四、Promise/A+规范
由于 Promise/A+规范较长,就不放到文章里了,给链接吧(中午版是自己翻译的,有出入的地方还请以英文原版为准)
对照promise/A+规范,以上的Promise代码还存在问题:
1.promise还需要存储promise状态和最终结果,以便后续被多次使用;
2.同一个promise的.then方法中注册的回调函数可被多次执行,且回调函数可以是个列表;
3.事件调度,回调函数应该在本轮.then方法所在事件队列结束后被调用;
4.捕捉错误并做拒绝处理;
更多细节...
继续改进,最后整改后的代码大致是这样的:
function Promise(fn) {
/* state
* 0 : pending
* 1 : resloved
* 2 : rejected
*/
var state = 0;
var value = null;
var handlers = []; function fulfill(result) {
state = 1;
value = result;
handlers.forEach(handle);
handlers = [];
}; function reject(error) {
state = 2;
value = error;
handlers.forEach(handle);
handlers = [];
}; function resolve(result) {
try {
var then = getThen(result);
if (then) {
init(then.bind(result), resolve, reject);
return;
}
fulfill(result);
} catch (err) {
reject(err);
}
}; function getThen(value) {
var type = typeof value;
if (value && (type === 'object' || type === 'function')) {
var then = value.then;
if (typeof then === 'function') {
return then;
}
}
return null;
}; function handle(handler) {
if (state === 0) {
handlers.push(handler);
} else {
if (typeof handler.onResolved === 'function') {
if (state === 1) {
handler.onResolved(value);
};
if (state === 2) {
handler.onRejected(value);
};
}
}
}; // 放到事件队列最后,在本轮事件执行完后执行
function timeHandle(callback, newValue) {
setTimeout(function () {
callback(newValue);
}, 0)
} function init(fn, onResolved, onRejected) {
try {
fn(function (value) {
timeHandle(onResolved, value);
}, function (reason) {
timeHandle(onRejected, reason);
});
} catch (err) {
timeHandle(onRejected, err);
}
}; init(fn, resolve, reject); this.then = function (onResolved, onRejected) {
if (!onResolved && !onRejected) {
throw new TypeError('One of onResolved or onRejected must be a function.')
};
return new Promise(function (resolve, reject) {
handle({
onResolved: function (result) {
if (typeof onResolved === 'function') {
try {
resolve(onResolved(result));
} catch (err) {
reject(err);
}
} else {
resolve(result);
}
},
onRejected: function (error) {
if (typeof onRejected === 'function') {
try {
resolve(onRejected(error));
} catch (err) {
reject(err);
}
} else {
reject(error);
}
}
})
})
};
} var firstPromise = new Promise(function (resolve, reject) {
setTimeout(function () {
var result = Math.random() <= 0.5 ? 1 : 0;
if (result) {
resolve('resolved 1');
} else {
reject('rejected 1');
}
}, 1000);
}) var secondPromise = new Promise(function (resolve, reject) {
setTimeout(function () {
var result = Math.random() <= 0.5 ? 1 : 0;
if (result) {
resolve('resolved 2');
} else {
reject('rejected 2');
}
}, 3000);
}) firstPromise.then(function (res) {
console.log('res 1 ', res);
return secondPromise;
}, function (err) {
console.log('rej 1 ', err);
return secondPromise;
}).then(function (res) {
console.log('res 2 ', res);
}, function (err) {
console.log('rej 2 ', err);
}) /* *
* 1s后输出 res 1 resolved 1 或者 rej 1 rejected 1
* 2s后输出 res 2 resolved 2 或者 rej 2 rejected 2
* */
通过板块一、二、三的知识点,即可大致摸清promise的实现;板块四加上一些补充和限制,遵循一些规范,提高promise功能的可扩展性。
学会了怎么理解promise,更重要的是学会正确的使用它。
正确使用 Promise
promise 在业务开发中多用来处理异步或者多层回调的情况。
基础使用 Promise MDN 及相关介绍文档中的案例为准,这里不一一赘述了... 这里简单的列出两个在使用 promise 过程中比较需要注意的点:
1. 不同平台环境下 Promise 的方法和遵循规则略微有些出入,详情以各平台环境下的 Promise 对象为基准。
如 es6 Promise 存在Promise.race,Promise.all等方法,node中则没有这些方法。
如 浏览器 Promise 事件调度走的是 setTimeout,node 走的是 process.nextTick 。(参考 [asap] )
2. Promise 虽可解决回调操作的规范问题(回调地狱),但也不能滥用 Promise (可能会占用过多内存)。
promise 解决后的结果会被存于内存中,被对应 promise 引用着,将上面的最终代码中测试的两个 promise 改成如下:
var firstPromise = new Promise(function (resolve, reject) {
setTimeout(function () {
var result = Math.random() <= 0.5 ? 1 : 0;
var str = '';
var i = 0, num = 500000;
for (; i < num; i++) {
str += 'promise '
}
if (result) {
resolve('resolved 1 : ' + str);
} else {
reject('rejected 1 : ' + str);
}
}, 1000);
})
则内存占用情况如下:
这些是一些平台差异或业务需求方面的不同点,对 Promise 核心实现并影响甚微,对 Promise 扩展方法有影响,对业务中 Promise 的使用有影响。
参考
分步理解 Promise 的实现的更多相关文章
- 大白话讲解Promise(二)理解Promise规范
上一篇我们讲解了ES6中Promise的用法,但是知道了用法还远远不够,作为一名专业的前端工程师,还必须通晓原理.所以,为了补全我们关于Promise的知识树,有必要理解Promise/A+规范,理解 ...
- 彻底理解Promise对象——用es5语法实现一个自己的Promise(上篇)
本文同步自我的个人博客: http://mly-zju.github.io/ 众所周知javascript语言的一大特色就是异步,这既是它的优点,同时在某些情况下也带来了一些的问题.最大的问题之一,就 ...
- 理解Promise的三种姿势
译者按: 对于Promise,也许你会用了,却并不理解:也许你理解了,却只可意会不可言传.这篇博客将从3个简单的视角理解Promise,应该对你有所帮助. 原文: Three ways of unde ...
- 理解Promise的3种姿势
译者按: 对于Promise,也许你会用了,却并不理解:也许你理解了,却只可意会不可言传.这篇博客将从3个简单的视角理解Promise,应该对你有所帮助. 原文: Three ways of unde ...
- 理解promise 02
1:promise是什么? 就是(链式)包装的回调函数. 2:语法 new Promise( function(resolve, reject) {...} /* executor */ ); exe ...
- 160701、理解 Promise 的工作原理
Javascript 采用回调函数(callback)来处理异步编程.从同步编程到异步回调编程有一个适应的过程,但是如果出现多层回调嵌套,也就是我们常说的厄运的回调金字塔(Pyramid of Doo ...
- 160623、理解 Promise 的工作原理
Javascript 采用回调函数(callback)来处理异步编程.从同步编程到异步回调编程有一个适应的过程,但是如果出现多层回调嵌套,也就是我们常说的厄运的回调金字塔(Pyramid of Doo ...
- 理解Promise简单实现的背后原理
在写javascript时我们往往离不开异步操作,过去我们往往通过回调函数多层嵌套来解决后一个异步操作依赖前一个异步操作,然后为了解决回调地域的痛点,出现了一些解决方案比如事件订阅/发布的.事件监听的 ...
- 理解promise 01
原文地址: http://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html 用Javascript的小伙伴们,是时候承认了,关于 ...
随机推荐
- winSockets编程(四)阻塞模式(服务端)
在阻塞模式下,在I/O操作完成前,执行的操作函数将一直等候而不会立即返回,该函数所在的线程会阻塞在这里.相反,在非阻塞模式下,套接字函数立即返回,而不管I/O是否完成. 重点知识和思想: ////// ...
- (转)私有代码存放仓库 BitBucket介绍及入门操作
转自:http://blog.csdn.net/lhb_0531/article/details/8602139 私有代码存放仓库 BitBucket介绍及入门操作 分类: 研发管理2013-02-2 ...
- JS鼠标滚动插件scrollpath使用介绍
JS鼠标滚动插件scrollpath:在这个插件中首先要引人的JS是jQuery,因为后面的JS都是基于它的.再者需要引入的是jquery.scrollpath.js.scrollpath.css还有 ...
- java基础-day6
第06天 java基础语法 今日内容介绍 u Eclipse断点调试 u 基础语法的练习 第1章 Eclipse断点调试 1.1 Eclipse断点调试概述 Eclipse的断点调试可以 ...
- XA: 事务和两阶段提交
本文原文连接:http://blog.csdn.net/bluishglc/article/details/7612811 ,转载请注明出处! 1.XA XA是由X/Open组织提出的两阶段提交协议, ...
- struts2 18拦截器详解(十)
ModelDrivenInterceptor 该拦截器处于defaultStack中的第九的位置,在ScopedModelDrivenInterceptor拦截器之后,要使该拦截器有效的话,Actio ...
- 如何获取帮助———— QQ群讨论摘要
QQ群对话整理(删除一些简单的回应),对一些重要的地方,我做了一些加粗 宝玉 2015/9/21 1:49:05 这次题目还有个问题就是如何读取Excel,我想对于很多同学来说是个困难 ...
- Elasticsearch 系列1 --- Windows10安装Elasticsearch
在Windows环境下,ES提供了两种安装方式,一种是通过MSI,特点是简单方便:另一种是绿色安装,解压zip包.本文选择第二种方式. 1. 准备工作 (1) Windows 10 (2) JDK 1 ...
- net 把指定 URI 的资源下载到本地
DirectoryInfo dir = new DirectoryInfo(AppContext.BaseDirectory); var path = dir.FullName + @"te ...
- WPF自定义TabControl样式
WPF自定义TabControl,TabControl美化 XAML代码: <TabControl x:Class="SunCreate.Common.Controls.TabCont ...