《JavaScript 标准参考教程(alpha)》by阮一峰
ES6原生提供了Promise对象。可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
为什么要使用Promise
Javascript的异步执行
javascript语言的执行环境是单线程的,即一次只能执行一个任务,如果有多个任务,就必须等待前面的任务执行完后才能执行下一个任务,如果前一个任务耗时很长,就会陷入阻塞,浏览器呈“假死”状态。比如,发送一个请求,等待请求返回的过程中这段时间就没有执行任何一个任务,白白占用了进程。
针对这种情况,javascript提出了同步和异步两种模式。
同步模式
即传统作法,每个任务按顺序执行,程序的执行顺序和任务的排列顺序是一致的。
异步模式
每一个任务分成两段,第一段代码包含对外部数据的请求,第二段代码被写成一个回调函数,包含了对外部数据的处理。
第一段代码执行完,不是立刻执行第二段代码,而是将程序的执行权交给第二个任务。等到外部数据返回了,再由系统通知执行第二段代码。
所以,程序的执行顺序与任务的排列顺序是不一致的、异步的。
异步编程的几种方法
回调函数
var f2 = function(){
console.log('f2');
}
var f1 = function(callback) {
console.log('f1');
setTimeout(function(){
callback();
},1000);
}
f1(f2);
console.log('外部');
结果:
f1
外部
f2
优点:简单、容易理解和部署
缺点:不利于代码的阅读和维护,各个部分之间高度耦合,使程序结构混乱,流程难以追踪,而且每个任务只能指定一个回调函数
事件监听:事件驱动模式
使用jquery来举例
f1.on('done', f2);
function f1(){
setTimeout(function () {
// f1的任务代码
f1.trigger('done'); //f1的代码执行完成后立即执行done事件
}, 1000);
}
优点:比较容易理解,可以绑定多个事件,每个事件可以指定多个回调函数,而且可以”去耦合“,有利于实现模块化。
缺点:整个程序都要变成事件驱动型,运行流程会变得很不清晰。
发布/订阅:观察者模式
“事件”可以理解成”信号”,如果存在一个”信号中心”,某个任务执行完成,就向信号中心”发布”一个信号,其他任务可以向信号中心”订阅”(subscribe)这个信号,从而知道什么时候自己可以开始执行。这就叫做”发布/订阅模式“,又称”观察者模式“。
具体方法略。可使用jquery的插件
异步操作的流程控制
我们提到异步模式的程序执行顺序与任务排列顺序并不确定是否一致,取决于向外部请求数据所耗费的时间长短。为了能控制顺序,有以下的方法:
ps:下面说的串行和并行针对的是对于异步任务的执行顺序,而上面说的同步、异步指的是异步还是同步
串行执行
function async(item, callback){
setTimeout(function(){
callback(item);
},1000);
}
function final() {
console.log(results);
}
var items = [ 1, 2, 3, 4, 5, 6 ];
var results = [];
function series(item) {
if(item) {
async( item, function(result) {
results.push(result);
return series(items.shift());
});
} else {
return final(results);
}
}
series(items.shift());
结果:
[1, 2, 3, 4, 5, 6]async
就是我们的异步任务,从结果中可以看出来,的确按照了我们的任务排列顺序执行了。也可以看作把异步任务按同步模式来执行。
并行执行
var a=1; //用来标记异步任务第一部分的执行顺序
var b=1; //用来标记异步任务第二部分的执行顺序
function async(item, callback){
console.log(a);
a++;
setTimeout(function(){
console.log('回调任务',b);
b++;
callback(item);
},2000);
}
function final() {
console.log(results);
}
var items = [ 1, 2, 3, 4, 5, 6 ];
var results = [];
items.forEach(function(item) {
async(item, function(result){
results.push(result);
if(results.length == items.length) {
final();
}
})
});
结果:
1
2
3
4
5
6
回调任务1
回调任务2
回调任务3
回调任务4
回调任务5
回调任务6
[1,2,3,4,5,6]
异步执行的意思是,全部任务同时进行,等所有异步任务都执行完后才执行final
函数。
但这样虽然加快了速度,却存在任务过多而造成资源消耗大,运行速度过慢的问题,因此我们需要控制一次同时执行任务的个数,也就是串行和并行相结合。
串行、并行相结合
var items = [ 1, 2, 3, 4, 5, 6 ];
var results = [];
var running = 0;
var limit = 2;
function launcher() {
while(running < limit && items.length > 0) {
var item = items.shift();
async(item, function(result) {
results.push(result);
running--;
if(items.length > 0) {
launcher();
} else if(running == 0) {
final();
}
});
running++;
}
}
launcher();
promise对象
为了用更简洁的代码实现上述异步编程的执行顺序,ES6就推出了promise对象
三种状态:未完成、已完成、失败
最终结果:成功——>传回一个值;失败——>抛出一个错误
var promise = new Promise(function(resolve, reject) {
// 异步操作的代码
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
var po = new Promise();
po.then(function(value) {
// success
}, function(value) {
// failure
});
如果省略then的第二个参数,则仅代表成功后的回调函数。
po.then(step1).then(step2).then(step3)
链式使用,一旦前面有一步错误,后面都不会执行
promise与ajax相结合
jquery的ajax
之前的写法:
$.ajax({
url: 'test.php', //请求地址
data: {'v': v}, //发送的数据
dataType: 'json',//返回的数据格式
type: 'get', //请求的方式:get|post
//发送前执行
beforeSend: function() {
console.log('beforeSend');
},
//返回成功执行
success: function(result) {
console.log(result);
},
//返回失败执行
error: function(err) {
console.log(err);
},
//无论成功还是失败都会执行
complete: function() {
console.log('complete');
},
//状态码对应的操作
statusCode: {
200: function() {},
404: function() {}
}
})
结合实际选择需要的选项
现在的ajax——Promise/Deferred模式
$.ajax({url: 'test.php'})
.success(function() {})
.error(function() {})
.success(function() {})
$.ajax()返回的就是promise对象
1.8版本后,jquery使用done(),fail()和always()分别对应着前面的三个方法
$.ajax({url: 'test.php'})
.done(function() {})
.fail(function() {})
.always(function() {})
可以用then()方法把done()和fail()合并在一起
promise.then(function() {
//done
}, function() {
//fail
})
如果只有一个参数,就表示done()方法
$.when()
$.when(deferreds):提供一种方法来执行一个或多个对象的回调函数,Deferred(延迟)对象通常表示异步事件
$.when( $.ajax({url: 'test.php'}) )
.then(function(data, status, jqXHR) {})
$.when(
$.ajax({url:'test.php'}),
$.ajax({url:'test1.php'})
)
.done(function(result, result1){
console.log(result);
console.log(result1);
})
.fail(function(err){
console.log("error");
})
当两个ajax都执行成功时就会调用done方法,否则只要其中一个失败就会执行fail方法。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。