ES6中的Promise

# 为什么出现 Promise
在 javascript 开发过程中,代码是单线程执行的,同步操作,彼此之间不会等待,这可以说是它的优势,但是也有它的弊端,如一些网络操作,浏览器事件,文件等操作等,都必须异步执行,针对这些情况,起初的操作都是使用回调函数实现。
实现方式如下(伪代码):
function One(callback) {
if (success) {
callback(err, result)
} else {
callback(err, null)
}
}
One(function(err, result) {
//执行完One函数内的内容,成功的结果回调回来向下执行
})
2
3
4
5
6
7
8
9
10
11
上述代码只是一层级回调,如果代码复杂后,会出现多层级的回调,代码可读性也会很差,那有没有一种方式,不用考虑里面的内容,直接根据结果成功还是失败执行下面的代码呢?有的,Promise(承诺),在 ES6 中对 Promise 进行了统一的规范。
# Promise 的含义
书上这么说:
Promise 是异步编程的一种解决方案,比传统的解决方案–回调函数和事件--更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了语法,原生提供了 Promise
所谓 Promise ,简单说就是一个容器,里面保存着某个未来才会结束的事件 (通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。 Promise 对象的状态不受外界影响
Promise/a+ 官方网站给出的定义 A promise represents the eventual result of an asyncchronous operation
翻译:表示一个异步操作的最终结果。
我的理解:
Promise 是回调函数可以规范的链式调用
# Promise 原理与讲解
# 原理
- Promise 的三种状态
- pending:进行中
- fulfilled :执行成功
- rejected :执行失败
注意 Promise 在某一时刻只能处于一种状态
- Promise 的状态改变
- pending------》fulfilled(resolved)
- pending------》rejected
Promise 的状态改变,状态只能由 pending 转换为 fulfilled(resolved)或者 rejected,一旦状态改变完成后将无法改变(不可逆性)
# 用代码讲原理
- 创建一个 Promise
创建 Promise 需要用到 Promise 的构造函数来实现,代码如下:
var promise=new Promise(function(resolve,reject){
// ...some async code
if(/* 一些异步操作成功*/)
{
resolve(value);
}else
{
reject(error);
}
})
2
3
4
5
6
7
8
9
10
11
12
代码分析:
- 在异步操作完成之后,会针对不同的返回结果调用 resolve 和 reject。
- resolve 和 reject 是两个函数,resolve 是异步操作成功时候被调用,将异步操作的返回值作为参数传递到外部;reject 是异步操作出异常时候被调用,将错误信息作为参数传递出去。
- Promise 其实没有做任何实质的代码操作,它只是对异步操作回调函数的不同结果定义了不同状态。
- resolve 函数和 reject 函数只是把异步结果传递出去
- 异步结果传递出去后,then 来接 Promise 对象将结果传递出来后,使用 then 方法来获取异步操作的值: 代码如下:
promise.then(
function(value) {
//success
},
function(error) {}
)
2
3
4
5
6
代码分析:
- then 方法将两个匿名函数作为参数,接收 resolve 和 reject 这两个函数的值。
- value 是执行成功的值,error 是执行出错时的错误信息。
- 对于 error 错误异常结果出现的时候,可以不单独写匿名错误的函数,可以直接用 catch 抛出
promise
.then(function(data) {
//success
})
.catch(function(error) {
//error
})
2
3
4
5
6
7
- 注意 then 方法只是用来获取异步操作的值。
- then 的返回值又是怎样呢? 先看一段调用两次 then 的代码:
//之前创建promise操作后
promise
.then(function(value) {
conlose.log(value) //有值
})
.then(function(value) {
conlose.log(value) //未定义
})
2
3
4
5
6
7
8
代码分析:
- 上面的第二个 then 方法中的值虽然是未定义,但是每一个 then 一定会返回一个新的 promise 对象,但是默认是一个空对象。
- 对于这个空对象我们如果想继续做一些什么,需要进行处理,可以用非空 Promise 对这个空的进行赋值覆盖,然后继续 then 的链式调用。
- then 中的return关键字很重要,联系着下一个 then 的调用。
# 几个常用 api
- Promise.resolve
resolve 方法用来将一个非 Promise 对象转化为 Promise 对象
转换的对象是一个常量或者不具备状态的语句,转换后的对象自动处于 resolve 状态。 转换的后的结果和原来一样
var promise = Promise.resolve('hello world')
promise.then(function(result) {
console.log(result) //输出结果 hello world
})
2
3
4
转换的对象如果直接是一个异步方法,不可以这么使用。
- Promise.all(常用 api)
多个 promise 需要执行的时候,可以使用 promise.all 方法统一声明,该方法可以将多个 Promise 对象包装成一个 Promise。
代码如下
promise
.all
//一系列promise操作
()
.then(function(results) {})
.catch(function(error) {})
2
3
4
5
6
代码分析:
- promise.all 对多个执行结果做一个包装传给了 then
- promise.all 中的执行顺序是怎么样的,Promise 的执行顺序是从被创建开始的,也就是在调用 all 的时候,所有的 promise 都已经开始执行了,all 方法只是等到所有的对象都执行完成,才会把结果传递给 then。
- all 中的 promise,如果有一个状态变成了 reject 那么转换后的 Promise 字节变成 reject,错误信息传递给 catch,不会传递给 then。(但是并不是说 all 这里面刚开始执行成功的操作就不算数了)
# Promise 在开发中的应用
项目开发中 promise 的应用代码:
Promise.all([
self.count({ phoneNumber: mobile, createdOn: { $gt: hour } }),
self.count({ ip: ip, createdOn: { $gt: hour } }),
]).then(function(results) {
if (results[0] >= 5) {
return callback({
code: -1,
message: '短信发送频率过快,每手机号1小时内只能发送5次',
})
}
if (results[1] >= 5) {
return callback({
code: -1,
message: '短信发送频率过快,每IP1小时内只能发送5次',
})
}
let code = {
phoneNumber: mobile,
code: tool.makeRandomStr(4, 1).toLowerCase(),
createdOn: new Date(),
expiredOn: new Date(new Date().getTime() + 20 * 60 * 1000), //20分钟失效
ip: ip,
isUsed: false,
}
self.create(code, function(err, newCode) {
if (newCode) {
sms.sendSMS(mobile, newCode.code, 'ali', function(err, body) {
console.log(body)
if (err) console.log('短信验证码发送失败:', err)
})
callback({ code: 0, message: '验证码已经发送' })
} else {
callback({ code: -1, message: '验证码发送失败,请重试' })
}
})
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
项目开发过程中使用 promise.all 的代码,当时是为了实现短信验证码发送前的校验功能。 all 中的两个 promise,第一个是统计时间内该手机号发送验证码数量;第二个是统计时间内该 ip 发送验证码的数量。
# Promise 使用过程中注意事项与误区
注意事项在上面原理讲解过程中,基本都提到过,只是重要的事情多说两遍。
- 状态不可逆性
- resolve 函数和 reject 函数只是传递异步结果
- then 进行层级调用的时候,每次的返回值都是一个空 promise 对象,如果想继续使用,赋值替换掉空 promise 对象,但是返回的时候 return 关键字很重要,不要忘了。
- promise.all 中的执行顺序是并行的,但是会等全部完成的结果传递给 then
- 执行顺序,promise 是 then 方法调用之后才会执行吗?还是从创建那一刻就开始执行? promise 从创建那一刻就开始执行,只是把结果传递给了 then,then 与 promise 的执行无关。
# Promise 的反思
Promise 的讲解就到这里,但是大家在开发过程中,会发现有些时候多次操作异步会出现很多层级的调用,也就是
promise.then(...)
.then(...)
.then(...)
2
3
4
5
这种情况,代码虽然看起来会比 callback 的回调简介和规范了很多,但是还是感觉一些复杂,有没有更好的解决办法呢?请看下一篇博客
回调的终极使用--async 和 await 的讲解
# 给我留言
← 类与继承 async和await讲解 →