ES6小记(5)
ES6小计(5)
15. Promise对象
15.1 Promise含义
Promise是一个用来传递异步操作的消息对象。Promise对象的两个特点:1. 对象的状态不受外界影响;2. 对象状态改变后就不会再次改变。Promise有3种状态,Pending(进行中)、Resolved(已完成)、Rejected(已失败)。状态改变只能从进行中->已完成、进行中->已失败这两种改变。
15.2 基本使用
Promise
是一个构造函数,用于生成Promise实例。构造函数接收一个函数作为参数,该函数两个参数分别为resolve
和reject
。它们是两个函数,由JS引擎提供,不用自己部署。resolve(data)
用于进行中->已完成的状态改变,并将改变时需要传递的值传递出去。reject(error)
用于进行中->已失败的状态改变,并将失败的错误信息传递出去。
var promise = new Promise(function(resolve, reject) {
if (true) {
resolve('ok')
} else {
reject('err')
}
})
resolve(data)
中的data除了普通值以外,还可以为另一个promise实例,此时,外层的promise实例的状态取决于传递的promise状态,并会将传递的实例传递的值传递出去。
let p1 = new Promise(function(resolve, reject) {
setTimeout(function() {
reject(1)
}, 1000);
})
let p2 = new Promise(function(resolve, reject) {
resolve(p1)
})
p2.then(function(data) {
console.log('ok');
console.log(data);
}, function(err) {
console.log('err');
console.log(err);
})
// err
// 1
15.3 Promise.prototype.then()
Promise实例的then()
方法,用于添加Promise状态改变后的回调。then(f1, f2)
接收两个函数参数,f1
是状态为已完成的回调,f2
是状态为已失败的回调。f2
不是必须的。
let p1 = new Promise(function(resolve, reject) {
setTimeout(function() {
reject(1)
}, 1000);
})
let p2 = new Promise(function(resolve, reject) {
resolve(2)
})
p1.then(function(data) {
console.log('ok:', data)
}, function(err) {
console.log('err:', err)
})
// err: 1
p2.then(function(data) {
console.log('ok:', data)
}, function(err) {
console.log('err:', err)
})
// ok: 2
then()
方法返回的是一个新的Promise对象实例,因此可以链式调用。then()
回调内的函数,默认最外层包裹了一次Promise构造函数。当then
内有遇到return
时,类似于执行了resolve
方法。因此,return
一个Promise对象实例也是可以的。
15.4 Promise.prototype.catch()
Promise实例的catch(fn)
方法用于指定发生错误时(失败时)的回调函数。Promise的失败状态有冒泡性质,会一直向后传递,直到被捕获。
let p1 = new Promise(function(resolve, reject) {
reject(2)
})
p1.then(function(data) {
console.log('ok:', data)
}).then(null, function(err) {
console.log('reject:', err)
}).catch(function(err) {
console.log('err:', err)
})
// reject: 2
上述例子中,一般不会这样处理,then
的第二个函数参数一般使用,而应用catch
进行统一捕获。Promise内部的错误在状态未改变之前,不会传递到Promise外。catch
方法也返回一个Promise对象,故后面可以继续使用then
、catch
方法。若catch
方法前没有报错,则会跳过catch
方法继续执行后面的调用链,若catch
后面的调用链出现错误,不会影响前面的catch
方法。
15.5 Promise.all()
Promise.all([p1, p2, ...pn])
方法用于将多个Promise实例包装成一个新的Promise实例。Promise.all()
的参数不一定是数组,只要有遍历器接口便可以。并且要求每一个返回的成员都是Promise实例,若不是,默认会用Promise.resolve()
将其转化为Promise实例。
返回的新的Promise实例的状态由传递的多个实例共同决定,1. 当多个实例状态全部为已完成时,新的实例状态才会变为已完成;后面的then
回调参数为所有实例对象返回传参组成的数组;2. 当多个实例中有一个实例状态变为已失败,新的实例状态会变为已失败;后面的错误捕获的传参为第一个错误的返回传参。
let p1 = new Promise(function(resolve, reject) {
resolve(1)
})
let p2 = new Promise(function(resolve, reject) {
resolve(2)
})
let p3 = new Promise(function(resolve, reject) {
reject(3)
})
Promise.all([p1, p2]).then(function(data) {
console.log('ok:', data);
}).catch(function(err) {
console.log('err:', err);
})
// ok: [1, 2]
Promise.all([p1, p3]).then(function(data) {
console.log('ok:', data);
}).catch(function(err) {
console.log('err:', err);
})
// err: 3
15.6 Promise.race()
Promise.race([p1, p2, ...pn])
方法同all()
的传参一致,不过返回的实例状态同传递的多个实例中第一个变化的状态一致。故取名为race。传递的返回参数也同第一个变化的返回参数一致。
15.7 Promise.resolve()
Promise.resolve(obj)
将传递的参数包装成Promise对象,若传参是一个普通类型,则会返回一个状态为已完成的Promise实例,并传递obj参数;
若传参是一个Promise对象,则会原封不动地将该Promise对象返回。
15.8 Promise.reject()
Promise.reject(obj)
将传递的参数包装成Promise对象,会返回一个状态为已失败的Promise实例,并传递obj参数,不管obj参数是什么类型。
16. 异步操作和async函数
异步与同步的概念不多说。对于异步的书写,ES6前主要用回调,但容易出现回调地狱的情况,不易阅读与维护。ES6出现了Promise,时写法有了顺序性,但需要自己包装异步函数,冗余代码较多。后续又有Generator状态机进行异步操作,但流程管理需要自己控制,也不方便。
16.1 co
模块
co(gen)
函数中,传参gen
为一个Generator函数,co
函数会返回一个Promise对象,故co().then().catch()
均为可行。co
函数要求yield
指令后必须传Promise对象或Thunk函数。
co(function *() {}).then(function(data) {}).catch(function(err) {})
当需要处理并发的异步操作时,可以用Promise.all()或者以对象或数组的形式进行书写。
co(function *() {
var res = yield [
Promise.resolve(1),
Promise.resolve(2)
]
return res
}) // 数组
co(function *() {
var res = yield {
1: Promise.resolve(1),
2: Promise.resolve(2)
}
return res
}) // 对象
co(function *() {
var res = yield Promise.all([
Promise.resolve(1),
Promise.resolve(2)
])
return res
}) // 对象
16.2 async函数
ES8中提供了async函数,使异步书写更为方便。async函数是Generator的语法糖。所有异步书写均为回调函数的语法糖。
var readFileAsync = async function () {
var f1 = await readFile('/file1')
var f2 = await readFile('/file2')
console.log(f1.toString())
console.log(f2.toString())
}
readFileAsync()
比较就会发现,async函数就是讲Generator函数的*
变为await
,将yield
变为await
,仅此而已。当然也有改进。
- async函数类似于
co
模块,自带执行器,不需要像Generator函数一样需要自己去控制流程执行。 - async包含的异步操作不会立即执行,需要像普通函数一样调用才会执行,不像
co
模块一样会立即执行,在需要的时候才会执行。 - 更好的语义,async直接表明是异步函数,await表示等待执行结果,比
*
和yield
更易理解。 - 更广的适应性,
co
模块的yield
语句后只能接Promise对象或Thunk函数,而await后面可以是Promise对象和原始类型的值。 返回值为Promise对象,同
co
模块。let p1 = Promise.resolve(1)
let p2 = Promise.reject(2)
let f = async function () {let a = await p1 let b = await p2 return {a, b}
}
f().then(function(data) {console.log(data);
}).catch(function(err) {
console.log('err:', err);
})
console.log(123);
// 123
// err: 2
注意async函数只是说明内部函数是异步函数,自身并不会让外部函数执行暂停,故先输出的时123。相较于co
函数来说,少了一层Promise的包装。await
不能用于普通函数中。
TO BE CONTINUED!