Promise

含义

对象有以下两个特点。

  • (1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
  • (2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

1、ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。

const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});

promise.then(function(value) {
// success
}, function(error) {
// failure
});

promise
.then(result => {
// ···
})
.catch(error => {
// ···
})
.finally(() => {
// ···
});
// 不管 Promise 对象最后状态如何,都会执行的操作

2、Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3]).then(([p1,p2,p3]) => {} );

上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。

(Promise.all方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。)

p的状态由p1、p2、p3决定,分成两种情况。

  • (1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
  • (2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

注意,如果作为参数的 Promise 实例,自己定义了catch方法(catch之后返回的依然是promise的resolve),那么它一旦被rejected,并不会触发Promise.all()的catch方法。

3、Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);

上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
Promise.race方法的参数与Promise.all方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve方法,将参数转为 Promise 实例,再进一步处理。

4、Promise.resolve将现有对象转为 Promise 对象

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))

Promise.resolve方法的参数分成四种情况。

  • (1)参数是一个 Promise 实例
    如果参数是 Promise 实例,那么Promise.resolve将不做任何修改、原封不动地返回这个实例。
  • (2)参数是一个thenable对象, thenable对象指的是具有then方法的对象,比如下面这个对象。
    let thenable = {
    then: function(resolve, reject) {
    setTimeout(resolve, 2000, 33);
    }
    };

    let p1 = Promise.resolve(thenable);
    p1.then(function(value) {
    console.log(value); // 2s后输出33
    });
    Promise.resolve方法会将这个对象转为 Promise 对象,然后就立即执行thenable对象的then方法。
  • (3)参数不是具有then方法的对象,或根本就不是对象
    如果参数是一个原始值,或者是一个不具有then方法的对象,则Promise.resolve方法返回一个新的 Promise 对象,状态为resolved。
  • (4)不带有任何参数
    Promise.resolve方法允许调用时不带参数,直接返回一个resolved状态的 Promise 对象。
    需要注意的是,立即resolve的 Promise 对象,是在本轮“事件循环”(event loop)的结束时,而不是在下一轮“事件循环”的开始时。
    setTimeout(function () {
    console.log('three');
    }, 0);

    Promise.resolve().then(function () {
    console.log('two');
    });
    console.log('one');
    // one
    // two
    // three

    上面代码中,setTimeout(fn, 0)在下一轮“事件循环”开始时执行,Promise.resolve()在本轮“事件循环”结束时执行,console.log(‘one’)则是立即执行,因此最先输出。

    5、Promise.reject(reason)方法也会返回一个新的 Promise 实例,该实例的状态为rejected。

ES2017 标准引入了 async 函数

Generator 函数的语法糖。

const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};

async函数对 Generator 函数的改进,体现在以下四点。

  • (1)内置执行器。

  • (2)更好的语义。

  • (3)更广的适用性。

  • (4)返回值是 Promise。

  • 1、async函数内部return语句返回的值,会成为then方法回调函数的参数。

  • 2、正常情况下,await命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。
    await命令后面的 Promise 对象如果变为reject状态,则reject的参数会被catch方法的回调函数接收到。

async function f() {
await Promise.reject('出错了');
}

f()
.then(v => console.log(v))
.catch(e => console.log(e))
// 出错了

注意,上面代码中,await语句前面没有return,但是reject方法的参数依然传入了catch方法的回调函数。这里如果在await前面加上return,效果是一样的。
任何一个await语句后面的 Promise 对象变为reject状态,那么整个async函数都会中断执行。

  • 3、使用注意点

    第一点,前面已经说过,await命令后面的Promise对象,运行结果可能是rejected,所以最好把await命令放在try…catch代码块中

    第二点,多个await命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。

// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);

// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;

第三点,await命令只能用在async函数之中,如果用在普通函数,就会报错。

第四点,async 函数可以保留运行堆栈。

Generator 函数(反正我是不会用这个的)

执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

有两个特征。

  • 一是,function关键字与函数名之间有一个星号;
  • 二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。
function* helloWorldGenerator() {
console.log(2222) yield 'hello';

console.log(3333) yield 'world';
return 'ending';
}

var hw = helloWorldGenerator();
hw.next() //只有调用next函数才会执行
// 2222{ value: 'hello', done: false }
hw.next()
// 3333{ value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }

调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。