ES6小计(5)

15. Promise对象

15.1 Promise含义

Promise是一个用来传递异步操作的消息对象。Promise对象的两个特点:1. 对象的状态不受外界影响;2. 对象状态改变后就不会再次改变。Promise有3种状态,Pending(进行中)、Resolved(已完成)、Rejected(已失败)。状态改变只能从进行中->已完成、进行中->已失败这两种改变。

15.2 基本使用

Promise是一个构造函数,用于生成Promise实例。构造函数接收一个函数作为参数,该函数两个参数分别为resolvereject。它们是两个函数,由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对象,故后面可以继续使用thencatch方法。若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,仅此而已。当然也有改进。

  1. async函数类似于co模块,自带执行器,不需要像Generator函数一样需要自己去控制流程执行。
  2. async包含的异步操作不会立即执行,需要像普通函数一样调用才会执行,不像co模块一样会立即执行,在需要的时候才会执行。
  3. 更好的语义,async直接表明是异步函数,await表示等待执行结果,比*yield更易理解。
  4. 更广的适应性,co模块的yield语句后只能接Promise对象或Thunk函数,而await后面可以是Promise对象和原始类型的值。
  5. 返回值为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!