重学es6 - 第六部分 | 8月更文挑战

261 阅读6分钟

前言

今天我们介绍一下es6引入的几个比较重要的东西. 他们能够让我们改善在es6之前我们遇到的某一些问题的痛点. 那么废话不多说, 直接进入主题吧.

generator 生成器函数

generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次

function *gen () {
    yield 1
    yield 2
    yield 3
}
let g = gen()
g.next() // {value: 1, done: false}
g.next() // {value: 2, done: false}
g.next() // {value: 3, done: false}
g.next() // {value: undefined, done: true}

生成器需要在function后加上一个*作为标识, 这里需要注意的是

  1. function* gen() {}
  2. function * gen() {}
  3. function *gen() {} 这三种写法都是合法的. generator生成器的行为是当你调用了next的时候,它就会走一步方法里边的yield的值. 如果走完了所有的yield的话,再调用next方法,则对返回一个valueundefined,donetrue的对象. 我们其实可以理解成yield是函数里边的断点. 当我们调用了next方法的时候,它就会走到下一个yield断点上. 由于这个特点,我们可以写一个无线迭代器
function *getTime() {
    while (true) {
        yield new Date().toLocaleTimeString()
    }
}
let getNow = getTime()
getNow.next()

大家可以尝试一下运行上面一段代码. 在我们执行getNow.next()的时候,我们永远可以拿到最新的本地时间.

Promise

Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值

Promise顾名思义它是承诺的意思, 一般用于异步上,因为异步发生在将来, 我们无法预见未来,所以我们可以先给一个承诺. 这和我们现实生活是一样一样的. 西式婚礼上,司仪会问男方:

XX,你愿意娶XX小姐为你的妻子么?照顾她,爱护她,无论贫穷还是富有,疾病还是健康,相爱相敬,不离不弃,永远在一起. 男: 我愿意!

用法:

首先Promise是一个对象,我们使用它的时候,需要先new一下它, Promise需要传递一个回调函数,回调函数可以传递两个参数进去, 第一个是resolve,第二个是reject.讲到这里,我们先说明一下Promise的几种状态.

  • 待定 (pending): 初始状态, 既没有被兑现,也没有拒绝
  • 已兑现 (fulfilled) : 成功
  • 已拒绝 (rejected) : 失败 下面我们一下代码了解一下具体的用法:
let p = new Promise((resolve, reject) => {
    console.log('1')
    resolve(true)
})
// 第一个p.then
p.then((val) => {
    // resolve的时候走这里
    console.log('2')
    return val + ' [true]'
}, (err) => {
    // reject的时候走这里
    console.log(err)
}).then((val) => {
    console.log(val + '[next then]')
})
// 第二个p.then
p.then((val) => {
    console.log('3')
    return val + ' [true]'
}, (err) => {
    console.log(err)
}).catch((err) => {
    console.log(err)
})
console.log('4')

// 1
// 4
// 2
// 3
// true [true][next then]

我们来分析一下上边我们写的实例.

  • 一开始的promise的状态是pending
  • 我们在回调函数内如果resolve了一个值出去的话, 则状态会变成fulfilled,]
  • 如果走了reject的方法的话,则状态会变成rejected. 需要注意的地方是,promise的状态是不可逆的,即: 一旦promise的状态从pending变成了其他的状态的话,则就永远是这个状态了. 我们可以看下一个例子:
let p = new Promise((resolve, reject) => {
    resolve(true)
    reject('err')
})
p.then((val) => {
    console.log(val)
}, (err) => {
    console.log(err)
})
// true
  • p.then方法接收两个回调函数,第一个是成功的回调函数,第二个是失败的回调函数.
  • 上面我们即执行了resolve方法也执行了reject方法,但是最后只走了成功的方法,并且状态是fulfilled. 说明如果状态变成了fulfilled的话,则不会再改成状态成rejected了.
  • 如果是走错误回调方法的话,另外除了reject一个值以外,我们还可以通过throw new Error('err')抛出一个错误的方式,走到失败回调函数里边.
    我们再看到第一个例子里边. 我们在里边console了一些东西, 最后返回的是1,4,2,3. 这说明了几个问题.
  1. new Promise里边走的回调函数是同步执行的
  2. p.then()里边的回调函数是异步执行的
  3. 如果有两个p.then()的话,他们会一起执行,顺序的话,则是按照代码的先走顺序执行.

Promise的其他方法

promise里边还有其他的很多方法,下面我们介绍一下比较常用的两个方法

all

Promise.all接收一个Promise的数组,返回的时候也是返回的一个Promise.then拿到的成功的值的数组. 下面我们看一下代码

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then((values) => {
  console.log(values);
});

// [3, 42, "foo"]

三个Promise放置在一个数组,传递给Promise.all, 等待三个Promise都成功的时候, 就会执行then函数拿到的值就是三个Promise成功返回的值. 值得一说的是,它必须要三个Promise都返回成功的时候,才会走then方法. Promise.all究竟有什么用呢? 我们来找一个它的使用场景, 比如有一个业务,需要满足两个条件,才会走下一步逻辑,而这两个逻辑可能都是异步的, 这时候,如果放在我们以前的话, 可能需要写一个全局的变量初始为0, 然后如果调用后台接口,返回成功的时候,我们去累加1,如果两个都成功的话,则是2. 因为不知道两个接口拿一个会先调用成功,所以我们需要在两个接口里边都写上一个函数, 如果计数的变量是2的情况,则走下一个逻辑. 这样写的代码,会有很多耦合的地方. 而且加上了全局变量污染, 并且我们还得考虑什么时候需要重置这个计数的变量. 写的代码就很容易出问题了. 但是有了Promise.all的话,我们就可以把两个接口都写在Promise.all里边,两个接口都返回成功了, 就会直接走then方法了.

race

Promise.racePromise.all的写法基本上是一致的, 如果理解了上面的Promise.all的话,race就很好理解了, race是一个竞态的方法. 就是数组里边传入的Promise谁先成功返回了数据后,就会调用then方法了.

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.race([promise1, promise2, promise3]).then((values) => {
  console.log(values);
});

// 3

结语

今天我们复习了一下generator它的使用方法,以及讲了一下Promise的写法和它的其他方法以及使用场景. Promise这个东西,我们还是要理解的比较深刻,后边无论是面试还是工作都很有用. 很多的东西其实都是基于Promise的. 比如es6以后的async,await概念,以及目前新版浏览器原生支持的fetch. 好了, 今天就复习到这里咯. 下次见~