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的用法

all方法的效果实际上是「谁跑的慢,以谁为准执行回调」,那么相对的就有另一个方法「谁跑的快,以谁为准执行回调」,这就是race方法,这个词本来就是赛跑的意思。race的用法与all一样,我们把上面runAsync1的延时改为1秒来看一下:
<!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>

看到没:只有第一个执行了回调!
在then里面的回调开始执行时,runAsync2()和runAsync3()并没有停止,仍旧再执行。于是再过1秒后,输出了他们结束的标志。
 
这个race有什么用呢?使用场景还是很多的,比如我们可以用race给某个异步请求设置超时时间,并且在超时后执行相应的操作。

再来看看jquery里面的$.Deferred:

        jquery用$.Deferred实现了Promise规范,$.Deferred是个什么玩意呢?还是老方法,打印出来看看,先有个直观印象:
var def = $.Deferred();
console.log(def);
输出如下:
$.Deferred()返回一个对象,我们可以称之为Deferred对象,上面挂着一些熟悉的方法如:done、fail、then等。jquery就是用这个Deferred对象来注册异步操作的回调函数,修改并传递异步操作的状态。
 
Deferred对象的基本用法如下,为了不与ajax混淆,我们依旧举setTimeout的例子:
<!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很像呢?

区别在何处一看便知。由于jquery的def对象本身就有resolve方法,所以我们在创建def对象的时候并未像ES6这样传入了一个函数参数,是空的。在后面可以直接def.resolve()这样调用。

<!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>

这样也有一个弊端,因为执行runAsync()可以拿到def对象,而def对象上又有resolve方法,那么岂不是可以在外部就修改def的状态了?比如我把上面的代码修改如下:
现象会如何呢?并不会在2秒后输出“执行完成”,而是直接输出“我穿越了”。因为我们在异步操作执行完成之前,没等他自己resolve,就在外部给resolve了。这显然是有风险的,比如你定义的一个异步操作并指定好回调函数,有可能被别人给提前结束掉,你的回调函数也就不能执行了。
 
怎么办?jquery提供了一个promise方法,就在def对象上,他可以返回一个受限的Deferred对象,所谓受限就是没有resolve、reject等方法,无法从外部来改变他的状态,用法如下:
<!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的链式调用

既然Deferred也是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.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

我们知道,Promise规范中,then方法接受两个参数,分别是执行完成和执行失败的回调,而jquery中进行了增强,还可以接受第三个参数,就是在pending状态时的回调,如下:
deferred.then( doneFilter [, failFilter ] [, progressFilter ] )
除此之外,jquery还增加了两个语法糖方法,done和fail,分别用来指定执行完成和执行失败的回调,也就是说这段代码:
d.then(function(){
console.log('执行完成');
}, function(){
console.log('执行失败');
});
与这段代码是等价的:
d.done(function(){
console.log('执行完成');
})
.fail(function(){
console.log('执行失败');
});

always的用法

jquery的Deferred对象上还有一个always方法,不论执行完成还是执行失败,always都会执行,有点类似ajax中的complete。不赘述了。
 

$.when的用法

jquery中,还有一个$.when方法来实现Promise,与ES6中的all方法功能一样,并行执行异步操作,在所有的异步操作执行完后才执行回调函数。不过$.when并没有定义在$.Deferred中,看名字就知道,$.when,它是一个单独的方法。与ES6的all的参数稍有区别,它接受的并不是数组,而是多个Deferred对象,如下:
$.when(runAsync(), runAsync2(), runAsync3())
.then(function(data1, data2, data3){
console.log('全部执行完成');
console.log(data1, data2, data3);
});
jquery中没有像ES6中的race方法吗?就是以跑的快的为准的那个方法。对的,jquery中没有。
以上就是jquery中Deferred对象的常用方法了,还有一些其他的方法用的也不多,干脆就不记它了。接下来该说说ajax了。
 

ajax与Deferred的关系

jquery的ajax返回一个受限的Deferred对象,还记得受限的Deferred对象吧,也就是没有resolve方法和reject方法,不能从外部改变状态。想想也是,你发一个ajax请求,别人从其他地方给你取消掉了,也是受不了的。
 
既然是Deferred对象,那么我们上面讲到的所有特性,ajax也都是可以用的。比如链式调用,连续发送多个请求:
req1 = function(){
return $.ajax(/*...*/);
}
req2 = function(){
return $.ajax(/*...*/);
}
req3 = function(){
return $.ajax(/*...*/);
} req1().then(req2).then(req3).done(function(){
console.log('请求发送完毕');
});

 

明白了ajax返回对象的实质,那我们用起来就得心应手了。
 

success、error与complete

这三个方法或许是我们用的最多的,使用起来是这样的:
$.ajax(/*...*/)
.success(function(){/*...*/})
.error(function(){/*...*/})
.complete(function(){/*...*/})
分别表示ajax请求成功、失败、结束的回调。这三个方法与Deferred又是什么关系呢?其实就是语法糖,success对应done,error对应fail,complete对应always,就这样,只是为了与ajax的参数名字上保持一致而已,更方便大家记忆,看一眼源码:
deferred.promise( jqXHR ).complete = completeDeferred.add;
jqXHR.success = jqXHR.done;
jqXHR.error = jqXHR.fail;
complete那一行那么写,是为了减少重复代码,其实就是把done和fail又调用一次,与always中的代码一样。deferred.promise( jqXHR )这句也能看出,ajax返回的是受限的Deferred对象。
 
jquery加了这么些个语法糖,虽然上手门槛更低了,但是却造成了一定程度的混淆。一些人虽然这么写了很久,却一直不知道其中的原理,在面试的时候只能答出一些皮毛,这是很不好的。这也是我写这篇文章的缘由。
 
 
 
 
看一个promise.js库:
/*!
* 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();
xhr.open(method,uri,true);
if(headers) {
for(var p in headers) {
xhr.setRequestHeader(p, headers[p]);
}
}
xhr.addEventListener('readystatechange',function(e){
if(xhr.readyState === 4) {
if(String(xhr.status).match(/^2\d\d$/)) {
resolve(xhr.responseText);
} else {
reject(xhr);
}
}
});
xhr.send(data);
})
} function wait(duration){
return new Promise(function(resolve, reject) {
setTimeout(resolve,duration);
})
} function waitFor(element,event,useCapture){
return new Promise(function(resolve, reject) {
element.addEventListener(event,function listener(event){
resolve(event)
this.removeEventListener(event, listener, useCapture);
},useCapture)
})
} function loadImage(src) {
return new Promise(function(resolve, reject) {
var image = new Image;
image.addEventListener('load',function listener() {
resolve(image);
this.removeEventListener('load', listener, useCapture);
});
image.src = src;
image.addEventListener('error',reject);})}function runScript(src){returnnewPromise(function(resolve, reject){var script = document.createElement('script');
script.src = src;
script.addEventListener('load',resolve);
script.addEventListener('error',reject);(document.getElementsByTagName('head')[0]|| document.body || document.documentElement).appendChild(script);})}function domReady(){returnnewPromise(function(resolve, reject){if(document.readyState ==='complete'){
resolve();}else{
document.addEventListener('DOMContentLoaded',resolve);}})}

看到了吗,Promise风格API跟回调风格的API不同,它的参数跟同步的API是一致的,但是它的返回值是个Promise对象,要想得到真正的结果,需要在then的回调里面拿到。

值得一提的是,下面这样的写法:

waitFor(document.documentElement,'click').then(function(){
console.log('document clicked!')
})

这样的事件响应思路上是比较新颖的,不同于事件机制,它的事件处理代码仅会执行一次。

通过这些函数的组合,我们可以更优雅地组织异步代码,请继续往下看。

三、使用Promise组织异步代码

函数调用/并行

Promise.all跟then的配合,可以视为调用部分参数为Promise提供的函数。譬如,我们现在有一个接受三个参数的函数

function print(a, b, c) {
console.log(a + b + c);
}

现在我们调用print函数,其中a和b是需要异步获取的:

var c = 10;

print(geta(), getb(), 10); //这是同步的写法

Promise.all([geta(), getb(), 10]).then(print); //这是 primise 的异步写法

竞争

如果说Primise.all是promise对象之间的“与”关系,那么Promise.race就是promise对象之间的“或”关系。

比如,我要实现“点击按钮或者5秒钟之后执行”

var btn = document.getElementsByTagName('button');

Promise.race(wait(5000), waitFor(btn, click)).then(function(){
console.log('run!')
})

异常处理

异常处理一直是回调的难题,而promise提供了非常方便的catch方法:

在一次promise调用中,任何的环节发生reject,都可以在最终的catch中捕获到

Promise.resolve().then(function(){
return loadImage(img1);
}).then(function(){
return loadImage(img2);
}).then(function(){
return loadImage(img3);
}).catch(function(err){
//错误处理
})

这非常类似于JS的try catch功能。

复杂流程

接下来,我们来看比较复杂的情况。

promise有一种非常重要的特性:then的参数,理论上应该是一个promise函数,而如果你传递的是普通函数,那么默认会把它当做已经resolve了的promise函数。

这样的特性让我们非常容易把promise风格的函数跟已有代码结合起来。

为了方便传参数,我们编写一个currying函数,这是函数式编程里面的基本特性,在这里跟promise非常搭,所以就实现一下:

function currying(){
var f = arguments[0];
var args = Array.prototype.slice.call(arguments,1);
return function(){
args.push.apply(args,arguments);
return f.apply(this,args);
}
}

currying会给某个函数"固化"几个参数,并且返回接受剩余参数的函数。比如之前的函数,可以这么玩:

var print2 = currying(print,11);

print2(2, 3); //得到 11 + 2 + 3 的结果,16

var wait1s = currying(wait,1000);

wait1s().then(function(){
console.log('after 1s!');
})

有了currying,我们就可以愉快地来玩链式调用了,比如以下代码:

Promise.race([
domReady().then(currying(wait,5000)),
waitFor(btn, click)])
.then(currying(runScript,'a.js'))
.then(function(){
console.log('loaded');
return Promise.resolve();
});

表示“domReady发生5秒或者点击按钮后,加载脚本并执行,完毕后打印loaded”。

四、总结

promise作为一个新的API,是几乎完全可polyfill的,这在JS发展中这是很少见的情况,它的API本身没有什么特别的功能,但是它背后代表的编程思路是很有价值的。

 
 

  

 
 
 
 

一.deferred对象简介

deferred对象是jquery回调函数的解决方案,解决了如何处理耗时操作的问题,对耗时操作提供了更好的控制,以及统一的编程接口。

二.语法

1.与ajax进行写法比较:

 1      //ajax传统写法
2 //$.ajax()接受一个对象参数,这个对象包含两个方法:
3 //success方法指定操作成功后的回调函数,error方法指定操作失败后的回调函数。
4 $.ajax({
5     url: "test.html",
6     success: function(){
7       alert("哈哈,成功了!");
8     },
9     error:function(){
10       alert("出错啦!");
11     }
12   });
13
14 //deferred对象写法
15 $.ajax("test.html")
16 .done(function(){alert("哈哈,成功了!");})
17 .fail(function(){alert("出错啦!");});
18 //可以看到,done()相当于success方法,fail()相当于error方法。采用链式写法以后,代码的可读性大大提高。

2.指定同一操作的多个回调函数

1 //deferred对象的一大好处,就是它允许你自由添加多个回调函数。
2 //回调函数可以添加任意多个,它们按照添加顺序执行。
3 $.ajax("test.html")
4   .done(function(){ alert("哈哈,成功了!");} )
5   .fail(function(){ alert("出错啦!"); } )
6   .done(function(){ alert("第二个回调函数!");} );

3.为多个操作指定回调函数

1 //deferred对象的另一大好处,就是它允许你为多个事件指定一个回调函数
2 $.when($.ajax("test1.html"), $.ajax("test2.html"))
3   .done(function(){ alert("哈哈,成功了!"); })
4   .fail(function(){ alert("出错啦!"); });
5 //这段代码的意思是,先执行两个操作$.ajax("test1.html")和$.ajax("test2.html")
6 //如果都成功了,就运行done()指定的回调函数;如果有一个失败或都失败了,就执行fail()指定的回调函数。
7 //when()参数的关系类似于逻辑运算“与”

4.deferred.resolve()方法和deferred.reject()方法

jQuery规定,deferred对象有三种执行状态----未完成,已完成和已失败。如果执行状态是"已完成"(resolved),deferred对象立刻调用done()方法指定的回调函数;如果执行状态是"已失败",调用fail()方法指定的回调函数;如果执行状态是"未完成",则继续等待,或者调用progress()方法(progress():当Deferred(延迟)对象生成进度通知时,调用添加处理程序。参数可以是一个单一的函数或函数数组。)指定的回调函数。

deferred.resolve()方法和deferred.reject()方法就是为了改变deferred对象的状态,从而决定执行哪个回调函数。

 1     var dtd = $.Deferred(); // 新建一个Deferred对象
2   var wait = function(dtd){
3   var tasks = function(){
4       alert("执行完毕!");
5       dtd.reject(); // 改变Deferred对象的执行状态
6     };
7     setTimeout(tasks,5000);
8     return dtd;
9   };
10   $.when(wait(dtd))
11   .done(function(){ alert("哈哈,成功了!"); })
12   .fail(function(){ alert("出错啦!"); });

5.deferred.promise()方法

第4点代码中的dtd对象是全局对象,所以它的执行状态可以从外部改变。

 1       var dtd = $.Deferred(); // 新建一个Deferred对象
2   var wait = function(dtd){
3     var tasks = function(){
4       alert("执行完毕!");
5       dtd.resolve(); // 改变Deferred对象的执行状态
6     };
7     setTimeout(tasks,5000);
8     return dtd;
9   };
10   $.when(wait(dtd))
11   .done(function(){ alert("哈哈,成功了!"); })
12   .fail(function(){ alert("出错啦!"); });
13   dtd.resolve();
14 //在代码的尾部加了一行dtd.resolve(),这就改变了dtd对象的执行状态,因此导致done()方法立刻执行,跳出"哈哈,成功了!"的提示框,等5秒之后再跳出"执行完毕!"的提示框

为了避免这种情况,jQuery提供了deferred.promise()方法。它的作用是,在原来的deferred对象上返回另一个deferred对象,后者只开放与改变执行状态无关的方法(比如done()方法和fail()方法),屏蔽与改变执行状态有关的方法(比如resolve()方法和reject()方法),从而使得执行状态不能被改变。

 1    var dtd = $.Deferred(); // 新建一个Deferred对象
2   var wait = function(dtd){
3   var tasks = function(){
4       alert("执行完毕!");
5       dtd.resolve(); // 改变Deferred对象的执行状态
6   };
7
8     setTimeout(tasks,5000);
9     return dtd.promise(); // 返回promise对象
10   };
11   var d = wait(dtd); // 新建一个d对象,改为对这个对象进行操作
12   $.when(d)
13   .done(function(){ alert("哈哈,成功了!"); })
14   .fail(function(){ alert("出错啦!"); });
15   d.resolve(); // 此时,这个语句是无效的
16 //在上面的这段代码中,wait()函数返回的是promise对象。然后,我们把回调函数绑定在这个对象上面,而不是原来的deferred对象上面。这样的好处是,无法改变这个对象的执行状态,要想改变执行状态,只能操作原来的deferred对象。
17
18 //或者可以将dtd对象变成wait()函数的内部对象:
19 var wait = function(dtd){
20     var dtd = $.Deferred(); //在函数内部,新建一个Deferred对象
21     var tasks = function(){
22       alert("执行完毕!");
23       dtd.resolve(); // 改变Deferred对象的执行状态
24     };
25
26     setTimeout(tasks,5000);
27     return dtd.promise(); // 返回promise对象
28   };
29   $.when(wait())
30   .done(function(){ alert("哈哈,成功了!"); })
31   .fail(function(){ alert("出错啦!"); });

6.普通操作的回调函数接口

(1)deferred对象的最大优点,就是它把这一套回调函数接口,从ajax操作扩展到了所有操作。也就是说,任何一个操作----不管是ajax操作还是本地操作,也不管是异步操作还是同步操作----都可以使用deferred对象的各种方法,指定回调函数。

使用$.when():参数只能是deferred对象。

假定有一个很耗时的操作wait,为它指定一个回调函数。

 1    var dtd = $.Deferred(); // 新建一个deferred对象
2   var wait = function(dtd){
3     var tasks = function(){
4       alert("执行完毕!");
5       dtd.resolve(); // 改变deferred对象的执行状态
6     };
7     setTimeout(tasks,5000);
8     return dtd;
9   };
10 //wait()函数返回的是deferred对象,可以加上链式操作。
11 $.when(wait(dtd))
12   .done(function(){ alert("哈哈,成功了!"); })
13   .fail(function(){ alert("出错啦!"); });
14 //wait()函数运行完,就会自动运行done()方法指定的回调函数。

(2)防止执行状态被外部改变的方法:使用deferred对象的建构函数$.Deferred(),

wait函数还是保持不变,我们直接把它传入$.Deferred():

$.Deferred()可以接受一个函数名作为参数,$.Deferred()所生成的deferred对象将作为这个函数的默认参数。

1     $.Deferred(wait)
2   .done(function(){ alert("哈哈,成功了!"); })
3   .fail(function(){ alert("出错啦!"); });

(3)可以直接在wait对象上部署deferred接口:

 1    var dtd = $.Deferred(); // 生成Deferred对象
2   var wait = function(dtd){
3   var tasks = function(){
4       alert("执行完毕!");
5       dtd.resolve(); // 改变Deferred对象的执行状态
6     };
7     setTimeout(tasks,5000);
8   };
9   dtd.promise(wait);//在wait对象上部署Deferred接口。正是因为有了这一行,后面才能直接在wait上面调用done()和fail()。
10   wait.done(function(){ alert("哈哈,成功了!"); })
11   .fail(function(){ alert("出错啦!"); });
12   wait(dtd);

7.总结

(1) $.Deferred() 生成一个deferred对象。

  (2) deferred.done() 指定操作成功时的回调函数

  (3) deferred.fail() 指定操作失败时的回调函数

  (4) deferred.promise() 没有参数时,返回一个新的deferred对象,该对象的运行状态无法被改变;接受参数时,作用为在参数对象上部署deferred接口。

  (5) deferred.resolve() 手动改变deferred对象的运行状态为"已完成",从而立即触发done()方法。

  (6)deferred.reject() 这个方法与deferred.resolve()正好相反,调用后将deferred对象的运行状态变为"已失败",从而立即触发fail()方法。

  (7) $.when() 为多个操作指定回调函数。

  (8)deferred.then():有时为了省事,可以把done()和fail()合在一起写,这就是then()方法。

1     $.when($.ajax( "/main.php" ))
2   .then(successFunc, failureFunc );

如果then()有两个参数,那么第一个参数是done()方法的回调函数,第二个参数是fail()方法的回调方法。如果then()只有一个参数,那么等同于done()。

  (9)deferred.always():这个方法也是用来指定回调函数的,它的作用是,不管调用的是deferred.resolve()还是deferred.reject(),最后总是执行。

1     $.ajax( "test.html" )
2   .always( function() { alert("已执行!");} );

文档参考:

 
 

彻底理解Javascript 中的 Promise(-------------------------------***---------------------------------)的更多相关文章

  1. 快速入门上手JavaScript中的Promise

    当我还是一个小白的时候,我翻了很多关于Promise介绍的文档,我一直没能理解所谓解决异步操作的痛点是什么意思 直到我翻了谷歌第一页的所有中文文档我才有所顿悟,其实从他的英文字面意思理解最为简单粗暴 ...

  2. 全面理解Javascript中Promise

    全面理解Javascript中Promise 最近在学习Promise的时候,在网上收集了一些资料,发现很多的知识点不够系统,所以小编特意为大家整理了一些自认为 比较好的文章,供大家更好地学习js中非 ...

  3. javascript中的promise和deferred:实践(二)

    javascript中的promise和deferred:实践(二) 介绍: 在第一节呢,我花了大量的时间来介绍promises和deferreds的理论.现在呢,我们来看看jquery中的promi ...

  4. 理解JavaScript中的原型继承(2)

    两年前在我学习JavaScript的时候我就写过两篇关于原型继承的博客: 理解JavaScript中原型继承 JavaScript中的原型继承 这两篇博客讲的都是原型的使用,其中一篇还有我学习时的错误 ...

  5. 深入理解JavaScript中创建对象模式的演变(原型)

    深入理解JavaScript中创建对象模式的演变(原型) 创建对象的模式多种多样,但是各种模式又有怎样的利弊呢?有没有一种最为完美的模式呢?下面我将就以下几个方面来分析创建对象的几种模式: Objec ...

  6. 深入理解JavaScript中的属性和特性

    深入理解JavaScript中的属性和特性 JavaScript中属性和特性是完全不同的两个概念,这里我将根据自己所学,来深入理解JavaScript中的属性和特性. 主要内容如下: 理解JavaSc ...

  7. 深入理解javascript中执行环境(作用域)与作用域链

    深入理解javascript中执行环境(作用域)与作用域链 相信很多初学者对与javascript中的执行环境与作用域链不能很好的理解,这里,我会按照自己的理解同大家一起分享. 一般情况下,我们把执行 ...

  8. 【干货理解】理解javascript中实现MVC的原理

    理解javascript中的MVC MVC模式是软件工程中一种软件架构模式,一般把软件模式分为三部分,模型(Model)+视图(View)+控制器(Controller); 模型:模型用于封装与应用程 ...

  9. 理解javascript中的策略模式

    理解javascript中的策略模式 策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换. 使用策略模式的优点如下: 优点:1. 策略模式利用组合,委托等技术和思想,有效 ...

随机推荐

  1. Eclipse下使用SVN插件从服务器获取工程

    1.打开Eclipse 4.4,在Eclipse里打开SVN资源库窗口:点击菜单window-->show view-->other:然后再弹出窗口展开SVN节点,选择SVN资源库   2 ...

  2. 微信公众号开发java框架:wx4j(入门篇)

    导航 入门 http://www.cnblogs.com/2333/p/6617819.html WxServlet介绍 MaterialUtils 素材工具类使用说明 http://www.cnbl ...

  3. HDU 1937 J - Justice League

    J - Justice League Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u ...

  4. POJ 2828 Buy Tickets | 线段树的喵用

    题意: 给你n次插队操作,每次两个数,pos,w,意为在pos后插入一个权值为w的数; 最后输出1~n的权值 题解: 首先可以发现,最后一次插入的位置是准确的位置 所以这个就变成了若干个子问题, 所以 ...

  5. 每天一个小算法(insertion sort3)

    今天多看看插入排序的理论部分. 先贴几个概念吧: 1.伪代码(英语:pseudocode),又称为虚拟代码,是高层次描述算法的一种方法.它不是一种现实存在的编程语言(已经出现了类似伪代码的语言,参见N ...

  6. 在Linux内核中添加系统调用,并编译内核

    1 环境准备 运行系统:vmware下安装的ubuntu10.10 32bit桌面版. 编译内核版本: linux-2.6.32.63 内核目录: /home/wanchouchou/linuxKer ...

  7. AI人工客服开发 小程序智能客服 智能客服微信小程序 智能客服系统怎么做 如何设计智能客服系统

    今天我们就来给大家分享下如何做 小程序的智能客服问答系统. 首先请确保你的小程序在线客服已经开通使用,并使用代码自己对接好了,将客户的提问自动做了拦截,拦截到了你自己开发的接口上. 做好了拦截以后,我 ...

  8. Linux命令之rhn_check

    NAME rhn_check - check for queued actions on RHN and execute them SYNOPSIS /usr/sbin/rhn_check [-v] ...

  9. TortoiseSVN里checkout depth各选项的含义

    代表四种检出深度: 1.Fully recursive——全递归:检出完整的目录树,包含所有的文件或子目录.2.Immediate children,including folders——直接子节点, ...

  10. python通过SSH登陆linux并操作

    使用python通过SSH登陆linux并操作 用的昨天刚接触到的库,在windows下通过paramiko来登录linux系统并执行了几个命令,基本算是初试成功,后面会接着学习的. 代码: > ...