2020-0305
请求串行执行

  1. function request_one() {
  2. return new Promise((resolve) => {
  3. setTimeout(() => {
  4. console.log("request_one_data");
  5. resolve("one");
  6. }, 3000);
  7. });
  8. }
  9. function request_two(res1) {
  10. return new Promise((resolve) => {
  11. setTimeout(() => {
  12. console.log("request_two_data");
  13. resolve(res1 + " " + "two");
  14. }, 2000);
  15. });
  16. }
  17. request_one()
  18. .then((res) => {
  19. return request_two(res);
  20. })
  21. .then((res) => {
  22. console.log(res);
  23. });

注意,不要写错:

  1. // 以下是错误的写法:
  2. function request_two(res1) {
  3. setTimeout(() => { // 错误,setTimeout应该在new Promise的里面,是在里面模拟异步请求!
  4. return new Promise((resolve) => {
  5. console.log("request_two_data");
  6. resolve(res1 + " " + "two");
  7. });
  8. }, 2000);
  9. }

请求并行执行,(一起执行,都resolved才返回)使用Promise.all方法:

  1. var promises = function () {
  2. return [1000, 2000, 3000].map(current => {
  3. return new Promise(function (resolve, reject) {
  4. setTimeout(() => {
  5. console.log(current)
  6. }, current)
  7. })
  8. })
  9. }
  10. Promise.all(promises()).then(() => {
  11. console.log('end')
  12. })

自行对比与async/await写法的异同。
MDN写的例子很好,几种情况都有:MDN例子__串行、并行等.js
有篇博文对其进行了封装,Promise的并行和串行
自己写的简单例子:语法简单例子.js


2020-08-26
Promise的特点
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。

Promise的作用
有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。

Promise的缺点
(1)无法取消Promise,一旦新建它就会立即执行;
(2)没有使用catch()方法指定错误处理的回调函数,Promise 对象抛出的错误不会传递到外层代码;

基本用法

  1. const promise = new Promise(function(resolve, reject) {
  2. // ... some code
  3. if (/* 异步操作成功 */){
  4. resolve(value);
  5. } else {
  6. reject(error);
  7. }
  8. });
  9. promise.then(function(value) {
  10. // success
  11. }, function(error) {
  12. // failure
  13. });

Promise 新建后就会立即执行。
异步加载图片的例子:

  1. function loadImageAsync(url) {
  2. return new Promise(function(resolve, reject) {
  3. const image = new Image();
  4. image.onload = function() {
  5. resolve(image);
  6. };
  7. image.onerror = function() {
  8. reject(new Error('Could not load image at ' + url));
  9. };
  10. image.src = url;
  11. });
  12. }

如果调用resolve函数和reject函数时带有参数,那么它们的参数会被传递给回调函数,即then()catch()函数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个 Promise 实例。

  1. const p1 = new Promise(function (resolve, reject) {
  2. // ...
  3. });
  4. const p2 = new Promise(function (resolve, reject) {
  5. // ...
  6. resolve(p1);
  7. })
  8. // 此时p1的状态会决定p2的状态

注意,调用resolvereject并不会终结 Promise 的参数函数的执行。

  1. new Promise((resolve, reject) => {
  2. resolve(1); // ''' 若加上return, 则不会执行后面的语句
  3. console.log(2); // '先输出2
  4. }).then(r => {
  5. console.log(r); // ''再输出1
  6. });
  7. // 2
  8. // 1

Promise.prototype.then()

采用链式的then,可以指定一组按照次序调用的回调函数。then方法的第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。通常只写第一个参数。

Promise.prototype.catch()

Promise.prototype.catch()方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

  1. getJSON('/posts.json').then(function(posts) {
  2. // ...
  3. }).catch(function(error) {
  4. // 处理 getJSON 和 前一个回调函数运行时发生的错误
  5. console.log('发生错误!', error);
  6. });
  1. // 写法一
  2. const promise = new Promise(function(resolve, reject) {
  3. try {
  4. throw new Error('test');
  5. } catch(e) {
  6. reject(e);
  7. }
  8. });
  9. // 写法二与一等价
  10. const promise = new Promise(function(resolve, reject) {
  11. reject(new Error('test'));
  12. });
  13. promise.catch(function(error) {
  14. console.log(error);
  15. });

如果 Promise 状态已经变成resolved,再抛出错误是无效的。因为 Promise 的状态一旦改变,就永久保持该状态。
Promise 内部的错误不会影响到 Promise 外部的代码:

  1. const someAsyncThing = function() {
  2. return new Promise(function(resolve, reject) {
  3. // 下面一行会报错,因为x没有声明
  4. resolve(x + 2); // 控制台会抛出错误,进入rejected状态,但依旧往下执行
  5. });
  6. };
  7. someAsyncThing().then(function() {
  8. console.log('everything is great');
  9. });
  10. setTimeout(() => { console.log(123) }, 2000); // 2秒后还是会输出
  11. // Uncaught (in promise) ReferenceError: x is not defined
  12. // 123

Node.js有一个unhandledRejection事件(未来会废除,更改为终止进程且退出码不为0),专门监听未捕获的reject错误:

  1. process.on('unhandledRejection', function (err, p) {
  2. throw err;
  3. });

Promise.prototype.finally()

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

  1. promise
  2. .then(result => {···})
  3. .catch(error => {···})
  4. .finally(() => {···});

Promise.all()

Promise.all()方法将多个 Promise 实例,包装成一个新的 Promise 实例。resolve回调执行是在所有输入的promise的resolve回调都结束;reject回调执行是,只要任何一个输入的promise的reject回调执行或者输入不合法的promise就会立即抛出错误,并且reject的是第一个抛出的错误信息。

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

(1)只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。
(2)任意一个为rejected状态,则p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

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

注意,如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()catch方法:

  1. const p1 = new Promise((resolve, reject) => {
  2. resolve('hello');
  3. })
  4. .then(result => result)
  5. .catch(e => e);
  6. const p2 = new Promise((resolve, reject) => {
  7. throw new Error('报错了');
  8. })
  9. .then(result => result)
  10. .catch(e => e);
  11. Promise.all([p1, p2])
  12. .then(result => console.log(result))
  13. .catch(e => console.log(e));
  14. // ["hello", Error: 报错了]

上面代码中,p1resolvedp2首先会rejected,但是p2有自己的catch方法,该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。该实例执行完catch方法后,也会变成resolved。如果p2没有自己的catch方法,就会调用Promise.all()catch方法。

Promise.race()

只要有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

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

Promise.allSettled()

接受一组 Promise 实例作为参数,只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束。该方法由 ES2020 引入。
Promise.allSettled()状态只可能变成fulfilled

  1. const resolved = Promise.resolve(42);
  2. const rejected = Promise.reject(-1);
  3. const allSettledPromise = Promise.allSettled([resolved, rejected]);
  4. allSettledPromise.then(function (results) {
  5. console.log(results);
  6. });
  7. // [
  8. // { status: 'fulfilled', value: 42 }, // 注意返回的格式
  9. // { status: 'rejected', reason: -1 }
  10. // ]

实际用处:若不关心异步操作的结果,只关心这些操作有没有结束。Promise.allSettled()方法就很有用。

Promise.any()

只要参数实例有一个变成fulfilled状态,包装实例就会变成fulfilled状态;如果所有参数实例都变成rejected状态,包装实例就会变成rejected状态。是一个第三阶段的提案
Promise.any()抛出的错误,是一个 AggregateError实例。它相当于一个数组,每个成员对应一个被rejected的操作所抛出的错误。

  1. var resolved = Promise.resolve(42);
  2. var rejected = Promise.reject(-1);
  3. var alsoRejected = Promise.reject(Infinity);
  4. Promise.any([resolved, rejected, alsoRejected]).then(function (result) {
  5. console.log(result); // 42
  6. });
  7. Promise.any([rejected, alsoRejected]).catch(function (results) {
  8. console.log(results); // [-1, Infinity]
  9. });

Promise.resolve()

该方法将现有对象转为 Promise 对象。
传入的参数分成4中情况:

  1. 参数为Promise实例:原封不动返回这个实例;
  2. 参数为具有then方法的对象:会将该对象转为Promise对象并立即执行then方法;
  3. 参数是原始值或不带then方法的对象:返回新的Promise对象,状态为resolved;
  4. 不带任何参数:则直接返回一个resolved状态的Promise对象;

注意:立即resolve()的 Promise 对象,是在本轮“事件循环”(event loop)的结束时执行,而不是下一轮开始时:

  1. setTimeout(function () {
  2. console.log('three');
  3. }, 0);
  4. Promise.resolve().then(function () {
  5. console.log('two');
  6. });
  7. console.log('one');
  8. // one
  9. // two
  10. // three

Promise.reject()

该方法也会返回一个新的 Promise 实例,该实例的状态为rejected
注意,Promise.reject()方法的参数,会原封不动地作为reject的理由,变成后续方法的参数。

Promise.try()

经常遇到一种情况:不知道或者不想区分,函数f是同步函数还是异步操作,但是想用 Promise 来处理它。因为这样就可以不管f是否包含异步操作,都用then方法指定下一步流程,用catch方法处理f抛出的错误。一般就会采用下面的写法:

  1. Promise.resolve().then(f)

但该方法有个缺点:若f是同步函数,则会在本轮事件循环末尾处进行,变成了异步执行。
那有没有方法,让同步函数同步执行,异步函数异步执行,且让它们有共同API?2种写法:

  1. async:

    1. const f = () => console.log('now');
    2. (async () => f())();
    3. console.log('next');
    4. // now
    5. // next
  2. 使用new Promise()

    1. const f = () => console.log('now');
    2. (
    3. () => new Promise(
    4. resolve => resolve(f())
    5. )
    6. )();
    7. console.log('next');
    8. // now
    9. // next

    于是有一个提案来替代上面的写法:Promise.try方法:

    1. const f = () => console.log('now');
    2. Promise.try(f);
    3. console.log('next');
    4. // now
    5. // next

    好处之一是可以更好地管理异常:

    1. function getUsername(userId) {
    2. return database.users.get({id: userId})
    3. .then(function(user) {
    4. return user.name;
    5. });
    6. }

    database.users.get()抛出一个异步错误,可用catch方法捕获;但也可能抛出同步错误(如数据库连接错误),不得的用try...catch去捕获错误。即变成:

    1. // 很笨拙的写法
    2. try {
    3. database.users.get({id: userId})
    4. .then(...)
    5. .catch(...)
    6. } catch (e) {
    7. // ...
    8. }
    9. // 换成Prmise.try(),统一用catch方法捕获错误
    10. Promise.try(() => database.users.get({id: userId}))
    11. .then(...)
    12. .catch(...)