课程来源:https://www.imooc.com/learn/949
1.Promise介绍
Promise是什么
1.主要用于异步计算
2.可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果。
3.可以在对象之间传递和操作Promise,帮助我们处理队列
Promise产生的背景
根源是为了优化表单提交的用户体验,而开发了JavaScript这款包含大量异步操作的脚本语言。
在提交表单中异步程序的表现是怎么样的呢?就是当你注册会员的时候,填写了昵称这玩意,然后再填写密码的时候,同时服务器里会检测这个昵称是否已经被注册从而做出一些回应,而不用等你全部信息填写好点击提交才告诉你昵称已经存在。
借由异步的这一个特点,可以想到:异步操作能够避免界面冻结!异步的本质用大白话说就是:将耗时很长的A交付的工作交给系统之后,就去继续做B交付的工作。等到系统完成前面的工作之后,再通过回调或者事件,继续做A交付的剩下的工作。
从观察者的角度看起来,AB工作的完成顺序,和交付它们的时间顺序无关,所以叫“异步”。
异步操作的常用语法
1.事件侦听与响应
// 事件侦听与响应
document.getElementById('start').addEventListener('click', start, false);
function start() {
// 响应事件,进行相应的操作
}
// jQuery 用 `.on()` 也是事件侦听
$('#start').on('click', start);
2.回调
// 回调
// 比较常见的有ajax
$.ajax('http://baidu.com', {
success: function (res) {
// 这里就是回调函数了
}
});
// 或者在页面加载完毕后回调
$(function(){
// 这里也是回调函数
});
JavaScript异步编程的4种方法:回调、事件监听、发布/订阅、Promise
具体可以参考:http://www.ruanyifeng.com/blog/2012/12/asynchronous_javascript.html
node.js 无阻塞和高并发,异步操作是其保障,大量操作依赖回调函数
以下才是Promise诞生的原因
1.解决因为异步操作所带来的回调地狱,从而导致维护性差,下面请看回调代码
a(function (resultsFromA) {
b(resultsFromA, function (resultsFromB) {
c(resultsFromB, function (resultsFromC) {
d(resultsFromC, function (resultsFromD) {
e(resultsFromD, function (resultsFromE) {
f(resultsFromE, function (resultsFromF) {
console.log(resultsFromF);
})
})
})
})
})
});
总结就是曾经的异步操作依赖的回调函数中存在着“嵌套层次深,难以维护”、“无法正常使用return和throw”、“无法正常检索堆栈信息”和“多个回调之间难以建立联系”这四个主要问题需要被解决,于是Promise横空出世。
2.Promise入门
new Promise(
/* 执行器 executor */
function (resolve, reject) {
// 一段耗时很长的异步操作
resolve(); // 数据处理完成
reject(); // 数据处理出错
}
).then(function A() {
// 成功,下一步
}, function B() {
// 失败,做相应处理
});
Promise的概念和优点
【优点】
Promise是一个代理对象,它和原先要进行的操作并无关系
Promise通过引入一个回调,避免了更多的回调
【状态】
pending:待定,称为初始状态
fulfilled:实现,称为操作成功状态
rejected:被否决,称为操作失败状态
当Promise状态发生改变的时候,就会触发.then()里的响应函数来处理后续步骤
Promise状态已经改变,就不会再变
Promise实例一经创建,执行器立即执行。
一个简单实例:
// 简单的范例-定时执行
console.log("here we go");
new Promise(function(resolve,reject){
setTimeout(function(){
console.log("Make me confused");
}, 2000); //定时函数设置好了后, 这个就算成功处理了数据
resolve("bad "); //所以调用resolve, 把"bad"传出去
})
.then(function(value){ //这个value接受到resolve传过来的"bad"
console.log(value + "example")
});
//不使用
setTimeout(()=>{
console.log("999");
callback("success");
},1000)
function callback(data){
console.log(data);
}
再看一个例子(两步执行):
console.log('here we go');
new Promise(resolve => {
setTimeout(() => {
resolve('hello');
}, 2000);
})
.then(value => {
console.log(value);
return new Promise(resolve => {
setTimeout(() => {
resolve('world');
}, 2000);
});
})
.then(value => {
console.log(value + ' world');
});
这个范例主要是简单的演示了Promise如何解决回调地狱这个让人头大的问题。
一个Promise完成了,再.then()会怎样
console.log('start');
let promise = new Promise(resolve => {
setTimeout(() => {
console.log('the promise fulfilled');
resolve('hello, world');
}, 1000);
});
setTimeout(() => {
promise.then(value => {
console.log(value);
});
}, 3000);
hello,world
讲师的原话:这段代码展示了Promise作为队列这个重要的特性,就是说我们在任何一个地方生成了一个Promise对象,都可以把它当做成一个变量传递到其他地方执行。不管Promise前面的状态到底有没有完成,队列都会按照固定的顺序去执行。
then()不返回Promise
console.log('here we go');
new Promise(resolve => {
setTimeout(() => {
resolve('hello');
}, 2000);
})
.then(value => {
console.log(value);
console.log('everyone');
(function () {
return new Promise(resolve => {
setTimeout(() => {
console.log('Mr.Laurence');
resolve('Merry Xmas');
}, 2000);
});
}());
return false;
})
.then(value => {
console.log(value + ' world');
});
我对以上代码的理解是这样的:最后一个then()方法里的value值代表的是上一个then()里的返回值,当没有return的时候,默认返回值为undefined。而resolve()里的数据为什么没被调用呢?因为上一个then()方法里return的是false而不是Promise实例。
要想调用resolve()里的数据,只要这么写就可以了
console.log('here we go');
new Promise(resolve => {
setTimeout(() => {
resolve('hello');
}, 2000);
})
.then(value => {
console.log(value);
console.log('everyone');
(function () {
return new Promise(resolve => {
setTimeout(() => {
console.log('Mr.Laurence');
resolve('Merry Xmas');
}, 2000);
});
}()).then(value => {
console.log(value + ' world');
});
})
3.then()
then()解析
then()接受两个函数作为参数,分别代表fulfilled和rejected
then()返回一个新的Promise实例,所以它可以链式调用
当前面的Promise状态改变时,then()根据其最终状态,选择特定的状态响应函数执行
状态响应函数可以返回新的Promise或其他值
如果返回新的Promise,那么下一级then()会在新的Promise状态改变之后执行
如果返回其他任何值,则会立刻执行下一级then()
看一个问题:
.then本来就会自动返回promise,为什么还有自己专门写return Promise呢?有什么实际用途吗?
解析:
then确实会返回一个promise,但是如果不手动return Promise,那么默认返回的promise状态就是resolved,值看你return的是啥了,不写return的话,值是undefined,return非promise,那么值就是这个非promise。而手动return Promise,那返回的promise状态就不一定是resolved了,因此就可以改变下一个then/catch调用的结果了。
then()的嵌套
then()里面有then()的情况:因为then()返回的还是Promise实例,故会等里面的then()执行完,再执行外面的,因此对于我们来说,此时最好将其展开,会更好的进行阅读。以下是then嵌套的代码
console.log('start');
new Promise(resolve => {
console.log('Step 1');
setTimeout(() => {
resolve(100);
}, 1000);
})
.then(value => {
return new Promise(resolve => {
console.log('Step 1-1');
setTimeout(() => {
resolve(110);
}, 1000);
})
.then(value => {
console.log('Step 1-2');
return value;
})
.then(value => {
console.log('Step 1-3');
return value;
});
})
.then(value => {
console.log(value);
console.log('Step 2');
});
解套后的代码为:
console.log('start');
new Promise(resolve => {
console.log('Step 1');
setTimeout(() => {
resolve(100);
}, 1000);
})
.then(value => {
return new Promise(resolve => {
console.log('Step 1-1');
setTimeout(() => {
resolve(110);
}, 1000);
})
})
.then(value => {
console.log('Step 1-2');
return value;
})
.then(value => {
console.log('Step 1-3');
return value;
})
.then(value => {
console.log(value);
console.log('Step 2');
});
嵌套的then并没有返回新的Promise实例只是传递resolve值(110),其执行顺序就像你写jquery链式调用一样。
Promise小测试
看以下代码进行分析四种Promise的区别是什么?是什么原因导致了不同的执行流程?
// 问题一
doSomething()
.then(function () {
return doSomethingElse();
})
.then(finalHandler);
//执行流程为doSomething ==> doSomethingElse(undefined) ==> finalHandler(resultDoSomethingELlse)
// 问题二
doSomething()
.then(function () {
doSomethingElse();
})
.then(finalHandler);
//执行流程为doSomething ==> doSomethingElse(undefined) ==> finalHandler(undefined)
//注意:doSomethingElse(undefined)和finalHandler(undefined)同时执行
// 问题三
doSomething()
.then(doSomethingElse())
.then(finalHandler);
//执行流程为doSomething ==> doSomethingElse(undefined) ==> finalHandler(resultOfDoSomething)
//注意:doSomethingElse(undefined)和doSomething()同时执行
// 问题四
doSomething()
.then(doSomethingElse)
.then(finalHandler);
//执行流程为doSomething ==> doSomethingElse(resultOfDoSomething) ==> finalHandler(resultOfDoSomethingElse)
Promise错误处理
因为在这一块,讲师貌似犯了些小错误,很多人反应很强烈,至于这错误到底是不是错误我也不太懂,但是不能把有异议的内容也写进来吧,于是我在网上找了篇自己能理解的Promise错误处理,贴上来给大家看看。
谈到Promise错误处理,就要把reject拿出来晾一晾了。reject的作用就是把Promise的状态置为rejected,这样我们在then中就能捕捉到,然后执行“失败”情况的回调(严格来说这不算是错误处理吧。。。),看下面的代码。
function getNumber() {
var p = new Promise(function(resolve, reject) {
//做一些异步操作
setTimeout(function() {
var num = Math.ceil(Math.random() * 10); //生成1-10的随机数
if(num <= 5) {
resolve(num);
} else {
reject('数字太大了');
}
}, 2000);
});
return p;
}
getNumber().then(function(data) {
console.log('resolved');
console.log(data);
}, function(reason) {
console.log('rejected');
console.log(reason);
});
getNumber函数用来异步获取一个数字,2秒后执行完成,如果数字小于等于5,我们认为是“成功”了,调用resolve修改Promise的状态。否则我们认为是“失败”了,调用reject并传递一个参数,作为失败的原因。
运行getNumber并且在then中传了两个参数,then方法可以接受两个参数,第一个对应resolve的回调,第二个对应reject的回调。所以我们能够分别拿到他们传过来的数据。多次运行这段代码,你会随机得到“成功”和“失败”的两种结果。
另一种处理错误和异常的方法:catch。 其实它和上面then的第二个参数一样,用来指定reject的回调,用法是这样的:
function getNumber() {
var p = new Promise(function(resolve, reject) {
//做一些异步操作
setTimeout(function() {
var num = Math.ceil(Math.random() * 10); //生成1-10的随机数
if(num <= 5) {
resolve(num);
} else {
reject('数字太大了');
}
}, 2000);
});
return p;
}
getNumber().then(function(data) {
console.log('resolved');
console.log(data);
}).catch(function(reason) {
console.log('rejected');
console.log(reason);
});
效果和写在then的第二个参数里面一样。不过它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中,请看下面的代码,然后分别代入自行测试一下
// 测试代码1
getNumber().then(function(data) {
console.log(name());
console.log('resolved');
console.log(data);
}, function(reason, data) {
console.log('rejected');
console.log(reason);
});
// 测试代码2
getNumber().then(function(data) {
console.log(name());
console.log('resolved');
console.log(data);
}).catch(function(reason) {
console.log('rejected');
console.log(reason);
});
在resolve的回调中,我们console.log(name());而name()这个函数是没有被定义的。如果我们不用Promise中的 catch,代码运行到这里就直接在控制台报错了,不往下运行,但是使用catch就不同了。
也就是说进到catch方法里面去了,而且把错误原因传到了reason参数中。即便是有错误的代码也不会报错了,这与我们的try/catch语句有相同的功能。
参考文章:
https://github.com/CruxF/Blog/issues/7
https://github.com/merrier/imooc-promise-sample