工作中对 Promise 和 Async 函数的一些使用心得

Promise

  1. resolve 若返回的是另一个 promise,则另一个 promise 的状态决定了前一个 promise 的状态
1
2
3
4
5
6
7
8
9
10
11
12
const p1 = new Promise((resolve, reject) => {
reject(new Error('fail'));
});
const p2 = new Promise((resolve, reject) => {
resolve(p1);
});
p2.then((result) => {
console.log(result);
}).catch((error) => {
console.log(error);
});
// Error:fail
  1. resolve 或 reject 不会终止 promise 参数函数的执行
1
2
3
4
5
6
7
8
new Promise((resolve, reject) => {
resolve(1);
console.log(2);
}).then((result) => {
console.log(result);
});
// => 2
// => 1

可以在 resolve 或 reject 前加上 return 来防止执行后面代码的意外。

  1. promise 对象一旦创建就会立即执行

Promise.prototype.then

  • then 方法第一个参数是 resolved 状态的回调函数,第二个参数 (可选) 是 rejected 状态的回调函数

  • then 方法若 return 一个新的 promise 对象,则下一个 then 方法会等待这个新的 promise 对象状态改变之后才会调用

Promise.prototype.catch

  • catch()方法返回的还是一个 Promise 对象,因此后面还可以接着调用then()方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const p1 = new Promise((resolve, reject) => {
reject('p1 rejection');
console.log(1); // => 第一次输出
});
const p2 = new Promise((resolve, reject) => {
// resolve('p2 resolve');
reject('p2 rejection');
});
p1.then((result) => {
console.log('p1 resolve');
return p2;
})
.catch((error) => {
console.log(error); // => 第二次输出
})
.then((result) => {
// console.log(result) //=> 输出为 undefined ,说明 catch 默认 return 了一个 resolve 为空的 promise 对象
console.log('p2 resolve'); // 第三次输出
})
.catch((error) => {
console.log('p2 rejection');
});
// => 1 p1 rejection p2 resolve
  • catch 方法后还可以继续调用 catch 方法来捕获上一个 catch 中的错误。

Promise.prototype.finally 方法

用于指定不管 Promise 对象最后状态如何,都会执行的操作。

  • finally方法的回调函数不接受任何参数,表明finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。

Promise.resolve

这个方法将现有对象转为 Promise 对象,该方法的执行结果根据参数不同有四种不同的情况

  1. 参数为一个 promise 实例,这时该方法原封不动的返回这个实例

  2. 参数不是具有 then 方法的对象,或根本不是对象(如原始值),则该方法返回一个新的 promise 对象,状态为 resolved

    1
    2
    3
    4
    const p = Promise.resolve('hello');
    p.then((result) => {
    console.log(result); // => hello
    });
  3. 参数为一个 thenable 对象,方法会将其转为 Promise 对象,然后立即执行 thenable 的 then 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let thenable = {
    then: function (resolve, reject) {
    resolve(2);
    },
    };
    let p1 = Promise.resolve(thenable);
    p1.then((result) => {
    console.log(result); // => 2
    });
  4. 方法中不带有参数,这时直接返回一个状态为 resolved 的 promise 对象

    1
    2
    3
    4
    const p = Promise.resolve();
    p.then(() => {
    //...
    });

    需要注意的是,立即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是在下一轮“事件循环”的开始时。(这话不好理解,待之后更新说明)

Promise.reject

同 resolve 一样返回一个 promise 实例,该实例状态为 rejected。

与 Promise.resolve 不一样的是,Promise.reject 方法的参数会作为后续方法的参数。

1
2
3
4
5
6
7
8
9
const thenable = {
then: (resolve, reject) => {
reject('error');
},
};
const p1 = Promise.reject(thenable);
p1.catch((err) => {
console.log(err == thenable); // true
});

上面代码中 catch 方法的参数不是 reject 抛出的 error 字符串,而是 thenable 对象。

Promise.all

将多个 Promise 实例,包装成一个新的 Promise 实例

1
const p = Promise.all([p1, p2, p3]);
  • p1、p2、p3 都是 Promise 实例,如果不是,则会默认调用 Promise.resolve()

  • 方法的参数可以不是数组,但必须具有 Iterator 接口。

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

    • 只有 p1、p2、p3 状态都变为 resolve,p 的状态才会 resolve,此时三个返回值组成数组传递给 p 的回调函数
    • 如果其中一个变为 rejected ,p 的状态就为 rejected,第一个被 reject 的返回值会传递给 p 的回调函数
  • 若参数中的 promise 实例都具有 catch 方法,则不会调用 Promise.all 的 catch 方法,因为执行完 catch 方法后会被 resolved

  • promise.all 的错误处理

    Promise.all 方法中如果其中一个 promise 的状态为 reject,则其他 resolve 状态的 promise 不会返回。

    若想接收所有状态的结果,可以对每个参数里的每个 promise 都加上 catch ,这样就不会进 Promise.all 的 catch ,实现如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    var promiseArray = [];
    promiseArray.push(promiseResove(1)); // 状态为 resolve 的 promise
    promiseArray.push(promiseReject(3)); // 状态为 reject 的 promise
    promiseArray.push(promiseResove(2)); // 状态为 resolve 的 promise

    // 将传入promise.all的数组进行遍历,如果catch住reject结果,
    // 直接返回,这样就可以在最后结果中将所有结果都获取到
    var handlePromise = Promise.all(
    promiseArray.map(function (promiseItem) {
    return promiseItem.catch(function (err) {
    return err;
    });
    })
    );
    handlePromise
    .then(function (values) {
    console.log('all promise are resolved', values);
    })
    .catch(function (reason) {
    // 不会走到 catch
    console.log('promise reject failed reason', reason);
    });

Async/Await

使用注意点

  1. async 函数返回的是一个 promise 对象,可以使用then方法添加回调函数。

  2. async函数返回的 Promise 对象,必须等到内部所有await命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到return语句或者抛出错误。即只有async函数内部的异步操作执行完,才会执行then方法指定的回调函数。

  3. await命令后面,可以是 Promise 对象或原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。

错误处理

  1. await 后的异步操作出错,等同于 async 函数返回的 promise 被 reject 。

  2. await 后面的 Promise 对象若变为 reject 状态 ,reject 参数会被 async 函数返回值的 catch 方法接收到,且当任何一个 await 后的 Promise 对象变为 reject,整个 async 函数都会中断执行,相当于 return 了这个状态为 reject 的 promise

1
2
3
4
5
aysnc function f() {
await Promise.reject('1'); // 相当于 return Promise.reject('1')
console.log('2'); // => 不会执行
await Promise.resolve('3') // => 不会执行
}

若希望前一个异步操作失败后不中断后续的异步操作,可将 await 放在 try catch 里。或者给 await 后的 Promise 加一个 catch 方法。

1
2
3
4
5
6
7
8
9
10
11
12
// 1.
async function f() {
try {
await f1();
} catch (e) {}
await f2();
}
// 2.
async function f() {
await f1().catch((err) => {});
await f2();
}
  1. await 后返回的是个 promise ,使用下面这种方式来进行错误处理更简洁.
1
2
3
4
5
6
7
8
9
10
11
// await-to.js 简易实现
function to(promise) {
return promise
.then((res) => [null, res])
.catch((err) => [err,null]);
}
async function fn() {
// 假设 f1 是一个异步处理函数
const [err,data] = await to(f1());
if(err) {...}
}

避免 async/await 滥用

分享一些关于 async/await 使用的讨论:

精读《async/await 是把双刃剑》。文章底部有 github 上的一个讨论链接。

如何避开 async/await 地狱