ES6原生提供了 Promise 对象。
到底是何方妖怪呢?打出来看看:
所谓 Promise,就是一个对象,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的 API,可供进一步处理。
Promise 对象有以下两个特点。
(1)对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和 Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是 Promise 这个名字的由来,它的英语意思就是「承诺」,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
有了 Promise 对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。此外,Promise 对象提供统一的接口,使得控制异步操作更加容易。
Promise 也有一些缺点。首先,无法取消 Promise,一旦新建它就会立即执行,无法中途取消。其次,如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。第三,当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
废话不多说,直接上demo:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Promise 学习笔记</title> <script type="text/javascript"> window.onload = function() { function pms1() { return new Promise(function(resolve, reject) { setTimeout(function() { console.log('执行任务1'); resolve('执行任务1成功'); }, 2000); }); } function pms2() { return new Promise(function(resolve, reject) { setTimeout(function() { console.log('执行任务2'); resolve('执行任务2成功'); }, 2000); }); } function pms3() { return new Promise(function(resolve, reject) { setTimeout(function() { console.log('执行任务3'); resolve('执行任务3成功'); }, 2000); }); } pms1().then(function(data) { console.log('第1个回调:' + data); return pms2(); }) .then(function(data) { console.log('第2个回调:' + data); return pms3(); }) .then(function(data) { console.log('第3个回调:' + data); return '还没完!该结束了吧!' }).then(function(data) { console.log(data); }); } </script> </head> <body> </body> </html>
怎么样?是不是灰常简单啊!
demo2:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <script type="text/javascript"> window.onload = function() { function pms1() { return new Promise(function(resolve, reject) { setTimeout(function() { var num = Math.ceil(Math.random() * 10); //生成1-10的随机数 if(num <= 5) { resolve(num); } else { reject('数字太大了吧!'); } }, 2000); }); } setInterval(function() { pms1().then(function(data) { //小于等于5的 console.log(data); }, function(data) { //大于的 console.log(data); }) }, 1000); } </script> </head> <body> </body> </html>
Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 方法和 reject 方法。
如果异步操作成功,则用 resolve 方法将 Promise 对象的状态,从「未完成」变为「成功」(即从 pending 变为 resolved);
如果异步操作失败,则用 reject 方法将 Promise 对象的状态,从「未完成」变为「失败」(即从 pending 变为 rejected)。
all的用法:
demo:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Promise 学习笔记</title> <script type="text/javascript"> window.onload = function() { function pms1() { return new Promise(function(resolve, reject) { setTimeout(function() { console.log('执行任务1'); resolve('执行任务1成功'); }, 2000); }); } function pms2() { return new Promise(function(resolve, reject) { setTimeout(function() { console.log('执行任务2'); resolve('执行任务2成功'); }, 2000); }); } function pms3() { return new Promise(function(resolve, reject) { setTimeout(function() { console.log('执行任务3'); resolve('执行任务3成功'); }, 2000); }); } Promise.all([pms1(), pms2(), pms3()]).then(function(data) { console.log(data); console.log({}.toString.call(data)); }) } </script> </head> <body> </body> </html>
用Promise.all来执行,all接收一个数组参数,里面的值最终都算返回Promise对象。这样,三个异步操作的并行执行的,等到它们都执行完后才会进到then里面。那么,三个异步操作返回的数据哪里去了呢?都在then里面呢,all会把所有异步操作的结果放进一个数组中传给then,就是上面的results。
race的用法
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Promise 学习笔记</title> <script type="text/javascript"> window.onload = function() { function pms1() { return new Promise(function(resolve, reject) { setTimeout(function() { console.log('执行任务1'); resolve('执行任务1成功'); }, 1000); }); } function pms2() { return new Promise(function(resolve, reject) { setTimeout(function() { console.log('执行任务2'); resolve('执行任务2成功'); }, 2000); }); } function pms3() { return new Promise(function(resolve, reject) { setTimeout(function() { console.log('执行任务3'); resolve('执行任务3成功'); }, 3000); }); } Promise.race([pms1(), pms2(), pms3()]).then(function(data) { console.log(data); //注意上面的延时时间 }) } </script> </head> <body> </body> </html>
再来看看jquery里面的$.Deferred:
var def = $.Deferred(); console.log(def);
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> <script type="text/javascript" src="js/jquery-3.1.1.min.js"></script> <script type="text/javascript"> $(function() { function runAsync() { var def = $.Deferred(); setTimeout(function() { console.log('执行完成'); def.resolve('随便什么数据'); }, 2000); return def; } runAsync().then(function(data) { console.log(data) }); }) </script> </head> <body> </body> </html>
在runAsync函数中,我们首先定义了一个def对象,然后进行一个延时操作,在2秒后调用def.resolve(),最后把def作为函数的返回。调用runAsync的时候将返回def对象,然后我们就可以.then来执行回调函数。
是不是感觉和ES6的Promise很像呢?
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> <script type="text/javascript" src="js/jquery-3.1.1.min.js"></script> <script type="text/javascript"> $(function() { function runAsync() { var def = $.Deferred(); setTimeout(function() { console.log('执行完成'); def.resolve('随便什么数据'); }, 2000); return def; } var pms=runAsync(); pms.then(function(data) { console.log(data) }); pms.resolve('我穿越了!') }) </script> </head> <body> </body> </html>
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> <script type="text/javascript" src="js/jquery-3.1.1.min.js"></script> <script type="text/javascript"> $(function() { function runAsync() { var def = $.Deferred(); setTimeout(function() { console.log('执行完成'); def.resolve('随便什么数据'); }, 2000); return def.promise(); } var pms=runAsync(); pms.then(function(data) { console.log(data) }); //pms.resolve('我穿越了!'); //这一句会报错jquery-3.1.1.min.js:2 Uncaught TypeError: pms.resolve is not a function }) </script> </head> <body> </body> </html>
then的链式调用
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> <script type="text/javascript" src="js/jquery-3.1.1.min.js"></script> <script type="text/javascript"> $(function() { function runAsync() { var def = $.Deferred(); setTimeout(function() { console.log('执行完成'); def.resolve('随便什么数据'); }, 2000); return def.promise(); } var pms = runAsync(); pms.then(function(data) { console.log('1:' + data); return runAsync(); }) .then(function(data) { console.log('2:' + data); return runAsync(); }) .then(function(data) { console.log('3:' + data); }); //pms.resolve('我穿越了!'); //这一句会报错jquery-3.1.1.min.js:2 Uncaught TypeError: pms.resolve is not a function }) </script> </head> <body> </body> </html>
done与fail
deferred.then( doneFilter [, failFilter ] [, progressFilter ] )
d.then(function(){ console.log('执行完成'); }, function(){ console.log('执行失败'); });
d.done(function(){ console.log('执行完成'); }) .fail(function(){ console.log('执行失败'); });
always的用法
$.when的用法
$.when(runAsync(), runAsync2(), runAsync3()) .then(function(data1, data2, data3){ console.log('全部执行完成'); console.log(data1, data2, data3); });
ajax与Deferred的关系
req1 = function(){ return $.ajax(/*...*/); } req2 = function(){ return $.ajax(/*...*/); } req3 = function(){ return $.ajax(/*...*/); } req1().then(req2).then(req3).done(function(){ console.log('请求发送完毕'); });
success、error与complete
$.ajax(/*...*/) .success(function(){/*...*/}) .error(function(){/*...*/}) .complete(function(){/*...*/})
deferred.promise( jqXHR ).complete = completeDeferred.add; jqXHR.success = jqXHR.done; jqXHR.error = jqXHR.fail;
/*! * Promise JavaScript Library v2.0.0 */ ; (function(window) { var _promise = function(thens) { this.thens = thens || []; this.state = ""; this._CONSTANT = { any: "any", number: "number", resolved: "resolved", rejected: "rejected", pending: "pending" }; }; _promise.prototype = { resolve: function() { if(this.state == this._CONSTANT.pending) { this.state = this._CONSTANT.resolved; return; } if(this.state !== "") return; if(this.promiseArr) { for(var i = 0, j = this.promiseArr.length; i < j; i++) { this.promiseArr[i].resolveCount++; } if(this.promiseArr[0].action !== this._CONSTANT.any) { if(this.resolveCount !== this.promiseArr.length) { return; } } else { if(this.resolveCount > 1) { return; } } } this.state = this._CONSTANT.resolved; if(!this.thens) return; if(this.thens[0] && this.thens[0].finallyCB) this.thens[0].finallyCB.apply(null, arguments); var t, n; while(t = this.thens.shift()) { if(typeof t === this._CONSTANT.number) { var self = this; setTimeout(function() { var prms = new _promise(self.thens); prms.resolve(); }, t); break; } var doneFn = t.done, action = t.action; if(!doneFn) continue; if(doneFn instanceof Array) { var arr = []; for(var i = 0, j = doneFn.length; i < j; i++) { var df = doneFn[i]; if(df instanceof _promise) { df.thens = this.thens; arr.push(df); } else { var m = df.apply(null, arguments); if(m instanceof _promise) { m.thens = this.thens; arr.push(m); } } } var l = arr.length; if(l === 0) { continue; } else { for(var i = 0; i < l; i++) { arr[i].promiseArr = arr; arr[i].action = action; arr[i].resolveCount = 0; } break; } } else { if(doneFn instanceof _promise) { doneFn.thens = this.thens; break; } else { n = doneFn.apply(null, arguments); if(n instanceof _promise) { n.thens = this.thens; break; } } continue; } } }, reject: function() { if(this.state !== "") return; if(this.promiseArr && this.promiseArr[0].action === this._CONSTANT.any) { if(this.promiseArr[this.promiseArr.length - 1] !== this) { return; } } this.state = this._CONSTANT.rejected; if(!this.thens) return; if(this.thens[0] && this.thens[0].finallyCB) this.thens[0].finallyCB.apply(null, arguments); var t, n; while(t = this.thens.shift()) { if(typeof t === this._CONSTANT.number) { var self = this; setTimeout(function() { var prms = new _promise(self.thens); prms.resolve(); }, t); break; } if(t.fail) { n = t.fail.apply(null, arguments); if(n instanceof _promise) { n.thens = this.thens; break; } continue; } break; } }, notify: function() { var t = this.thens[0]; t.progress.apply(null, arguments); }, then: function(done, fail, progress) { this.thens.push({ done: done, fail: fail, progress: progress }); return this; }, any: function(done, fail, progress) { this.thens.push({ done: done, fail: fail, progress: progress, action: this._CONSTANT.any }); return this; }, done: function(done) { if(this.thens.length === 0 || this.thens[this.thens.length - 1].done) { this.thens.push({ done: done }); } else { this.thens[this.thens.length - 1].done = done; } return this; }, fail: function(fail) { if(this.thens.length === 0 || this.thens[this.thens.length - 1].fail) { this.thens.push({ fail: fail }); } else { this.thens[this.thens.length - 1].fail = fail; } return this; }, progress: function(progress) { if(this.thens.length === 0 || this.thens[this.thens.length - 1].progress) { this.thens.push({ progress: progress }); } else { this.thens[this.thens.length - 1].progress = progress; } return this; }, ensure: function(finallyCB) { if(this.thens.length === 0 || this.thens[this.thens.length - 1].finallyCB) { this.thens.push({ finallyCB: finallyCB }); } else { this.thens[this.thens.length - 1].finallyCB = finallyCB; } return this; }, always: function(alwaysCB, progress) { this.thens.push({ done: alwaysCB, fail: alwaysCB, progress: progress }); return this; }, wait: function(ms) { this.thens.push(~~ms); return this; } } var Promise = function(parameter) { var prms = new _promise(); if(parameter) { if(arguments.length > 1) { prms.thens[0] = {}; prms.thens[0].done = []; prms.thens[0].done.push.apply(prms.thens[0].done, arguments); setTimeout(function() { prms.resolve(); }, 1) } else { prms = parameter(); if(prms instanceof _promise) return prms; } } return prms; }; Promise.when = function() { var prms = new _promise(); prms.thens[0] = {}; prms.thens[0].done = []; prms.thens[0].done.push.apply(prms.thens[0].done, arguments); setTimeout(function() { prms.resolve(); }, 1) return prms; }; Promise.any = function() { var prms = new _promise(); prms.thens[0] = {}; prms.thens[0].action = prms._CONSTANT.any; prms.thens[0].done = []; prms.thens[0].done.push.apply(prms.thens[0].done, arguments); setTimeout(function() { prms.resolve(); }, 1) return prms; }; Promise.timeout = function(promise, ms) { setTimeout(function() { promise.reject(); }, ms); return promise; } Promise.gtTime = function(promise, ms) { promise.state = promise._CONSTANT.pending; setTimeout(function() { if(promise.state == promise._CONSTANT.resolved) { promise.state = ""; promise.resolve(); } promise.state = ""; }, ms); return promise; } if(typeof module === "object" && module && typeof module.exports === "object") { module.exports = Promise; } else { window.Promise = Promise; if(typeof define === "function" && define.amd) { define("promise", [], function() { return Promise; }); } } }(window));
promise.js提供了done和resolve方法,done负责注册成功的回调函数,resolve负责触发。
function cb() {
alert('success')
}
var prms = Promise()
prms.done(cb)
setTimeout(function() {
prms.resolve()
}, 3000)
在3秒之后,浏览器将alert “success”。
当然你也可以通过prms.resolve(“xxx”)传递参数给cb函数使用,如:
function cb(num) {
alert(num)
}
var prms = Promise()
prms.done(cb)
setTimeout(function() {
prms.resolve(1)
}, 3000)
在3秒之后,浏览器将alert “1”。
fail/reject
fail函数负责注册失败的回调函数,reject负责触发。如:
function cb() {
alert('fail')
}
var prms = Promise()
prms.fail(cb)
setTimeout(function () {
prms.reject()
}, 3000)
progress/notify
progress函数负责注册处理中进度的回调函数,notify负责触法。如:
function cb() {
alert('progress')
}
var prms = Promise()
prms.progress(cb)
setInterval(function() {
prms.notify()
}, 2000)
每隔两秒浏览器会弹出一个progress。
chain
function cb1() {
alert('success')
}
function cb2() {
alert('fail')
}
function cb3() {
alert('progress')
}
var prms = Promise();
prms.done(cb1).fail(cb2).progress(cb3)
setTimeout(function () {
prms.resolve()
//prms.reject()
//prms.notify()
}, 3000)
then
function fn1() {
alert('success')
}
function fn2() {
alert('fail')
}
function fn3() {
alert('progress')
}
var prms = Promise()
prms.then(fn1, fn2, fn3)
prms.resolve()
prms.reject()
prms.notify()
当然也支持prms.then().then().then()……….
当then的第一个参数为一个数组的时候,要等所有task都完成:
f1().then([f2_1, f2_2]).then(f3)
如上面的代码:
f1执行完后,同时执行f2_1和f2_2,当f2_1和f2_2全部都执行完成才会执行f3。
any
f1().any([f2_1, f2_2]).then(f3)
f1执行完后,同时执行f2_1和f2_2,当f2_1和f2_2中的任意一个执行完成才会执行f3。
always
var prms = Promise()
prms.always(function () {
alert(2)
})
setTimeout(function () {
// prms.resolve()
prms.reject()
}, 3000)
always(fn)等同于then(fn,fn),也等同于done(fn).fail(fn)
wait
function f10() {
var promise = Promise();
setTimeout(function () {
console.log(10);
promise.resolve();
}, 4500)
return promise;
}
function f11() {
var promise = Promise();
setTimeout(function () {
console.log(11);
promise.resolve();
}, 1500)
return promise;
}
f11().wait(5000).then(f10) //execute f11 then wait 5000ms then execute f10
ensure
ensure方法类似try…catch..finally中的finally,不管task成功失败都会执行。
Promise.when
Promise.when(f1(), f2()).then(f3).then(f4)
function f1() {
var promise = Promise();
setTimeout(function () {
console.log(1);
promise.resolve("from f1");
}, 1500)
return promise;
}
function f2() {
var promise = Promise();
setTimeout(function () {
console.log(2);
promise.resolve();
}, 5500)
return promise;
}
function f3() {
var promise = Promise();
setTimeout(function () {
console.log(3);
promise.resolve();
}, 1500)
return promise;
}
function f4() {
var promise = Promise();
setTimeout(function () {
console.log(4);
promise.resolve();
}, 1500)
return promise;
}
上面promise.when的等同简略写法也可以是:Promise(f1(),f2()).then….
Promise.any
Promise.any的使用和when一样,when的意义是等所有task都完成再执行后面的task,而any的意义是任何一个task完成就开始执行后面的task。
Promise.timeout
Promise.timeout(f1(), 2000).then(f2, function () {
alert("timeout");
}).wait(5000).then(f3);
function f1() {
var promise = Promise();
setTimeout(function () {
console.log(1);
promise.resolve("from f1");
}, 1500)
return promise;
}
function f2() {
var promise = Promise();
setTimeout(function () {
console.log(2);
promise.resolve();
}, 1500)
return promise;
}
function f3() {
var promise = Promise();
setTimeout(function () {
console.log(3);
promise.resolve();
}, 1500)
return promise;
}
with wind.js
<script src="wind-all-0.7.3.js"></script>
<script src="promise.js"></script>
<script>
Wind.Promise.create = function (fn) {
var prms = Promise();
fn(prms.resolve, prms.reject);
return prms;
}
var testAsync = eval(Wind.compile("promise", function () {
for (var i = 0; i < 3; i++) { //loop 3 times
var aa = $await(f1());
alert(aa); //alert “from f1”
$await(f2().wait(3000)); //execute f2 then wait 3000ms
$await(f3());
}
}));
testAsync();
function f1() {
var promise = Promise();
setTimeout(function () {
console.log(1);
promise.resolve("from f1");
}, 2500)
return promise;
}
function f2() {
var promise = Promise();
setTimeout(function () {
console.log(2);
promise.resolve();
}, 1500)
return promise;
}
function f3() {
var promise = Promise();
setTimeout(function () {
console.log(3);
promise.resolve();
}, 1500)
return promise;
}
</script>
That’s all.Have Fun!
用Promise组织程序
一、Promise基本用法
很多文章介绍Promise给的例子是这样的:
new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('POST', location.href, true);
xhr.send(null);
xhr.addEventListener('readystatechange', function(e){
if(xhr.readyState === 4) {
if(xhr.status === 200) {
resolve(xhr.responseText);
} else {
reject(xhr);
}
}
})
}).then(function(txt){
console.log();
})
一定会有小朋友好奇,说尼玛,这不是比回调还恶心?
这种写法的确是能跑得起来啦……不过,按照Promise的设计初衷,我们编程需要使用的概念并非"Promise对象",而是promise函数,凡是以Promise作为返回值的函数,称为promise函数(我暂且取了这个名字)。所以应该是这样的:
function doSth() {
return new Promise(function(resolve, reject) {
//做点什么异步的事情
//结束的时候调用 resolve,比如:
setTimeout(function(){
resolve(); //这里才是真的返回
},1000)
})
}
如果你不喜欢这样的写法,还可以使用defer风格的promise
function doSth2() {
var defer = Promise.defer();
//做点什么异步的事情
//结束的时候调用 defer.resolve,比如:
setTimeout(function(){
defer.resolve(); //这里才是真的返回
},1000)
return defer.promise;
}
总之两种是没什么区别啦。
然后你就可以这么干:
doSth().then(doSth2).then(doSth);
这样看起来就顺眼多了吧。
其实说简单点,promise最大的意义在于把嵌套的回调变成了链式调用(详见第三节,顺序执行),比如以下
//回调风格
loadImage(img1,function(){
loadImage(img2,function(){
loadImage(img3,function(){
});
});
});
//promise风格
Promise.resolve().then(function(){
return loadImage(img1);
}).then(function(){
return loadImage(img2);
}).then(function(){
return loadImage(img3);
});
后者嵌套关系更少,在多数人眼中会更易于维护一些。
二、Promise风格的API
在去完cssconf回杭州的火车上,我顺手把一些常见的JS和API写成了promise方式:
function get(uri){
return http(uri, 'GET', null);
}
function post(uri,data){
if(typeof data === 'object' && !(data instanceof String || (FormData && data instanceof FormData))) {
var params = [];
for(var p in data) {
if(data[p] instanceof Array) {
for(var i = 0; i < data[p].length; i++) {
params.push(encodeURIComponent(p) + '[]=' + encodeURIComponent(data[p][i]));
}
} else {
params.push(encodeURIComponent(p) + '=' + encodeURIComponent(data[p]));
}
}
data = params.join('&');
}
return http(uri, 'POST', data || null, {
"Content-type":"application/x-www-form-urlencoded"
});
}
function http(uri,method,data,headers){
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest