Promise机制
Promise的诞生与Javascript中异步编程息息相关,js中异步编程主要指的是setTimout/setInterval、DOM事件机制、ajax,通过传入回调函数实现控制反转。异步编程为js带来强大灵活性的同时,也带来了嵌套回调的问题。详细来说主要有两点,第一嵌套太深代码可读性太差,第二并行逻辑必须串行执行。
request = function(url, cb, eb) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if ((xhr.status >=200 && xhr.status < 300) || xhr.status === 304) {
cb(xhr.responseText);
} else {
eb(new Error({
message: xhr.status
}));
}
}
};
xhr.open('get', url, true);
xhr.send(null);
}
这个例子中程序要依次处理data1、data2、data3,嵌套太多可读性太差
request = function(url) {
var def = new Deferred(); var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if ((xhr.status >=200 && xhr.status < 300) || xhr.status === 304) {
def.resolve(xhr.responseText)
} else {//简化ajax,没有提供错误回调
def.reject(new Error({
message: xhr.status
}));
}
}
};
xhr.open('get', url, true);
xhr.send(null); return def.promise;
} request('data1.json').then(function(data1) {
console.log(data1);//处理data1
return request('data2.json');
}).then(function(data2) {
console.log(data2);//处理data2
return request('data3.json');
}, function(err) {
console.error(err);
}).then(function(data3) {
console.log(data3);
alert('success');
}, function(err) {
console.error(err);
});
这个例子中程序需要请求data1、data2、data3数据,得到三个数据后才进行下一步处理。数据并不需要串行请求,但我们的代码却需要串行执行,增加了等待时间。
//并行逻辑串行执行
request('data1', function(data1) {
request('data2', function(data2) {
request('data3', function(data3) {
console.log(data1, data2, data3);//处理全部数据 alert('success');
}, function(err) {
console.error(err);
});
}, function(err) {
console.error(err);
});
}, function(err) {
console.error(err);
});
Promise机制
Promise机制便是上述问题的一种解决方案。与他相关的规范有PromiseA和PromiseA+,PromiseA中对Promise进行了整体描述,PromiseA+对A进行了补充,在then函数的行为方面进行了更加详尽的阐述。
then
method, which registers callbacks to receive either a promise’s eventual value or the reason why the promise cannot be fulfilled.跟promise交互的主要方式是通过他的then方法来注册回调函数去接收promise的最终结果值或者是promise不能完成的原因。我们可以简单总结一下规范。每个promise都有三个状态:pending(默认)、fulfilled(完成)、rejected(失败);默认状态可以转变为完成态或失败态,完成态与失败态之间无法相互转换,转变的过程是不可逆的,转变一旦完成promise对象就不能被修改。通过promise提供的then函数注册onFulfill(成功回调)、onReject(失败回调)、onProgres(进度回调)来与promise交互。Then函数返回一个promise对象(称为promise2,前者成为promise1),promise2受promise1状态的影响,具体请查看A+规范。
上两个规范中并没有说明promise的状态如何改变,大部分前端框架中使用Deferred来改变promise的状态(resolve()、reject())。二者关系请看下图。
这里根据规范,我们实现一下promise
Promise = function() {
this.queue = [];
this.value = null;
this.status = 'pending';// pending fulfilled rejected
}; Promise.prototype.getQueue = function() {
return this.queue;
};
Promise.prototype.getStatus = function() {
return this.status;
};
Promise.prototype.setStatus = function(s, value) {
if (s === 'fulfilled' || s === 'rejected') {
this.status = s;
this.value = value || null;
this.queue = [];
var freezeObject = Object.freeze || function(){};
freezeObject(this);// promise的状态是不可逆的
} else {
throw new Error({
message: "doesn't support status: " + s
});
}
};
Promise.prototype.isFulfilled = function() {
return this.status === 'fulfilled';
};
Promise.prototype.isRejected = function() {
return this.status === 'rejected';
}
Promise.prototype.isPending = function() {
return this.status === 'pending';
}
Promise.prototype.then = function(onFulfilled, onRejected) {
var handler = {
'fulfilled': onFulfilled,
'rejected': onRejected
};
handler.deferred = new Deferred(); if (!this.isPending()) {//这里允许先改变promise状态后添加回调
utils.procedure(this.status, handler, this.value);
} else {
this.queue.push(handler);//then may be called multiple times on the same promise;规范2.2.6
}
return handler.deferred.promise;//then must return a promise;规范2.2.7
}; var utils = (function(){
var makeSignaler = function(deferred, type) {
return function(result) {
transition(deferred, type, result);
}
}; var procedure = function(type, handler, result) {
var func = handler[type];
var def = handler.deferred; if (func) {
try {
var newResult = func(result);
if (newResult && typeof newResult.then === 'function') {//thenable
// 此种写法存在闭包容易造成内存泄露,我们通过高阶函数解决
// newResult.then(function(data) {
// def.resolve(data);
// }, function(err) {
// def.reject(err);
// });
//PromiseA+规范,x代表newResult,promise代表def.promise
//If x is a promise, adopt its state [3.4]:
//If x is pending, promise must remain pending until x is fulfilled or rejected.
//If/when x is fulfilled, fulfill promise with the same value.
//If/when x is rejected, reject promise with the same reason.
newResult.then(makeSignaler(def, 'fulfilled'), makeSignaler(def, 'rejected'));//此处的本质是利用了异步闭包
} else {
transition(def, type, newResult);
}
} catch(err) {
transition(def, 'rejected', err);
}
} else {
transition(def, type, result);
}
}; var transition = function(deferred, type, result) {
if (type === 'fulfilled') {
deferred.resolve(result);
} else if (type === 'rejected') {
deferred.reject(result);
} else if (type !== 'pending') {
throw new Error({
'message': "doesn't support type: " + type
});
}
}; return {
'procedure': procedure
}
})(); Deferred = function() {
this.promise = new Promise();
}; Deferred.prototype.resolve = function(result) {
if (!this.promise.isPending()) {
return;
} var queue = this.promise.getQueue();
for (var i = 0, len = queue.length; i < len; i++) {
utils.procedure('fulfilled', queue[i], result);
}
this.promise.setStatus('fulfilled', result);
}; Deferred.prototype.reject = function(err) {
if (!this.promise.isPending()) {
return;
} var queue = this.promise.getQueue();
for (var i = 0, len = queue.length; i < len; i++) {
utils.procedure('rejected', queue[i], err);
}
this.promise.setStatus('rejected', err);
}
通过Promise机制我们的编程方式可以变成这样:
request = function(url) {
var def = new Deferred(); var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if ((xhr.status >=200 && xhr.status < 300) || xhr.status === 304) {
def.resolve(xhr.responseText)
} else {//简化ajax,没有提供错误回调
def.reject(new Error({
message: xhr.status
}));
}
}
};
xhr.open('get', url, true);
xhr.send(null); return def.promise;
} request('data1.json').then(function(data1) {
console.log(data1);//处理data1
return request('data2.json');
}).then(function(data2) {
console.log(data2);//处理data2
return request('data3.json');
}, function(err) {
console.error(err);
}).then(function(data3) {
console.log(data3);
alert('success');
}, function(err) {
console.error(err);
});
对于并行逻辑串行执行问题我们可以这样解决
//所有异步操作都完成时,进入完成态,
//其中一项异步操作失败则进入失败态
all = function(requestArray) {
// var some = Array.prototype.some;
var def = new Deferred();
var results = [];
var total = 0;
requestArray.some(function(r, idx) {
//为数组中每一项注册回调函数
r.then(function(data) {
if (def.promise.isPending()) {
total++;
results[idx] = data; if (total === requestArray.length) {
def.resolve(results);
}
}
}, function(err) {
def.reject(err);
});
//如果不是等待状态则停止,比如requestArray[0]失败的话,剩下数组则不用继续注册
return !def.promise.isPending();
}); return def.promise;
} all(
[request('data1.json'),
request('data2.json'),
request('data3.json')]
).then(
function(results){
console.log(results);// 处理data1,data2,data3
alert('success');
}, function(err) {
console.error(err);
});
以下是几个测试案例
//链式调用
var p1 = new Deferred();
p1.promise.then(function(result) {
console.log('resolve: ', result);
return result;
}, function(err) {
console.log('reject: ', err);
return err;
}).then(function(result) {
console.log('resolve2: ', result);
return result;
}, function(err) {
console.log('reject2: ', err);
return err;
}).then(function(result) {
console.log('resolve3: ', result);
return result;
}, function(err) {
console.log('reject3: ', err);
return err;
});
p1.resolve('success');
//p1.reject('failed');
p1.promise.then(function(result) {
console.log('after resolve: ', result);
return result;
}, function(err) {
console.log('after reject: ', err);
return err;
}).then(function(result) {
console.log('after resolve2: ', result);
return result;
}, function(err) {
console.log('after reject2: ', err);
return err;
}).then(function(result) {
console.log('after resolve2: ', result);
return result;
}, function(err) {
console.log('after reject2: ', err);
return err;
}); //串行异步
var p2 = new Deferred();
p2.promise.then(function(result) {
var def = new Deferred();
setTimeout(function(){
console.log('resolve: ', result);
def.resolve(result);
})
return def.promise;
}, function(err) {
console.log('reject: ', err);
return err;
}).then(function(result) {
var def = new Deferred();
setTimeout(function(){
console.log('resolve2: ', result);
def.reject(result);
})
return def.promise;
}, function(err) {
console.log('reject2: ', err);
return err;
}).then(function(result) {
console.log('resolve3: ', result);
return result;
}, function(err) {
console.log('reject3: ', err);
return err;
});
p2.resolve('success'); //并行异步
var p1 = function(){
var def = new Deferred();
setTimeout(function() {
console.log('p1 success');
def.resolve('p1 success');
}, 20); return def.promise;
}
var p2 = function(){
var def = new Deferred();
setTimeout(function() {
console.log('p2 failed');
def.reject('p2 failed');
}, 10); return def.promise;
} var p3 = function(){
var def = new Deferred();
setTimeout(function() {
console.log('p3 success');
def.resolve('p3 success');
}, 15); return def.promise;
} all([p1(), p2(), p3()]).then(function(results) {
console.log(results);
}, function(err) {
console.error(err);
});
Promise优点
对比使用Promise前后我们可以发现,传统异步编程通过嵌套回调函数的方式,等待异步操作结束后再执行下一步操作。过多的嵌套导致意大利面条式的代码,可读性差、耦合度高、扩展性低。通过Promise机制,扁平化的代码机构,大大提高了代码可读性;用同步编程的方式来编写异步代码,保存线性的代码逻辑,极大的降低了代码耦合性而提高了程序的可扩展性。
参考文章:
jQuery 2.0.3 源码分析 Deferred(最细的实现剖析,带图)
Promise机制的更多相关文章
- 闲话Promise机制
Promise的诞生与Javascript中异步编程息息相关,js中异步编程主要指的是setTimout/setInterval.DOM事件机制.ajax,通过传入回调函数实现控制反转.异步编程为js ...
- [React Native]Promise机制
React Native中经常会看到Promise机制. Promise机制代表着在JavaScript程序中下一个伟大的范式.可以把一些复杂的代码轻松撸成一个串,和Android中的rxjava非常 ...
- Promise的前世今生和妙用技巧
浏览器事件模型和回调机制 JavaScript作为单线程运行于浏览器之中,这是每本JavaScript教科书中都会被提到的.同时出于对UI线程操作的安全性考虑,JavaScript和UI线程也处于同一 ...
- 一道关于Promise应用的面试题
题目:红灯三秒亮一次,绿灯一秒亮一次,黄灯2秒亮一次:如何让三个灯不断交替重复亮灯?(用Promse实现) 三个亮灯函数已经存在: function red(){ console.log('red') ...
- Javascript异步编程之三Promise: 像堆积木一样组织你的异步流程
这篇有点长,不过干货挺多,既分析promise的原理,也包含一些最佳实践,亮点在最后:) 还记得上一节讲回调函数的时候,第一件事就提到了异步函数不能用return返回值,其原因就是在return语句执 ...
- nodejs与Promise的思想碰撞
玩node的同志们都知道,当这门语言被提出来的时候,作为自己最为骄傲的异步机制,却被PHP和Python等战团喷得不成样子的是,他们嘲笑着nodejs那蠢蠢的无限嵌套,nodejs战团只能以我们只要性 ...
- promise、resolve、reject、拦截响应
Promise是一个接口,它用来处理的对象具有这样的特点:在未来某一时刻(主要是异步调用)会从服务端返回或者被填充属性.其核心是,promise是一个带有then()函数的对象. 使用promise机 ...
- 《React-Native系列》3、RN与native交互之Callback、Promise
接着上一篇<React-Native系列>RN与native交互与数据传递,我们接下来研究另外的两种RN与Native交互的机制 一.Callback机制 首先Calllback是异步的, ...
- Promise编程规范
参考: http://www.cnblogs.com/dojo-lzz/p/4340897.html 闲话promise机制 http://www.cnblogs.com/lvdabao/p/es6 ...
随机推荐
- 关于GameObject.activeInHierarchy,activeSelf,SetActive
activeSelf(read only只读):物体本身的active状态,对应于其在inspector中的checkbox是否被勾选activeInHierarchy(read only只读):物体 ...
- (转)linux中fork()函数详解
一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同, ...
- 关于Switch结构利用
三大流程结构,循环.分支.if ,循环与条件选择结构用的比较多,而swicth用的比较少,swicth可以用if代替,只不过麻烦,最终都能实现输入和输出的条件对应 Swicth利用 ...
- 如何利用jQuery进行简单表单验证
<!DOCTYPE html><html><head><meta charset="utf-8"><meta name=&qu ...
- python 读取文本
将文本转换到NumPy 数组中,做机器学习或其他任何任务,文本处理的技能必不可少.python 实现实现了很精简强大的文本处理功能: 假设 文件 traindata.csv 中有数据 1000行,3列 ...
- $(document).ready() 与 window.onload的区别
1.执行时间 window.onload 必须等到页面内所有元素(包括图片 css js等)加载完毕后才会执行. $(document).ready() 是DOM结构绘制完毕后就执行,不必等到所有元素 ...
- C++学习14 继承的概念及语法
继承是类与类之间的关系,是一个很简单很直观的概念,与现实世界中的继承(例如儿子继承父亲财产)类似. 继承(Inheritance)可以理解为一个类从另一个类获取成员变量和成员函数的过程.例如类B继承于 ...
- log4j 缓存
log4j.appender.A2.bufferedIO=truelog4j.appender.A2.bufferSize=512000
- MongoDB 多条件组合查询
组合条件查询json格式语法 { "$and": [ { "Date": { $gt: ISODate("2015-06-05T00:45:00.00 ...
- POJ 3264-Balanced Lineup, NYOJ 119-士兵杀敌3 线段树
士兵杀敌(三) 时间限制:2000 ms | 内存限制:65535 KB 难度:5 描述 南将军统率着N个士兵,士兵分别编号为1~N,南将军经常爱拿某一段编号内杀敌数最高的人与杀敌数最低的人进行比 ...