[TOC]

理解Promise对象

Promise对象用于异步操作,表示一个现在、将来或永不可能可用的值。有如下几个特点:

  1. Promise对象代表了未来某个将要发生的事件(通常是一个异步操作);
  2. 通过Promise对象,可以将异步操作以同步的流程表达出来,避免了层层嵌套的回调函数(回调地狱);
  3. Promise是一个构造函数,用来生成promise实例。
$.get('https://api.github.com/users', data => {
   console.log('fetched all users');
   const {login, url} = data[0];
   $.get(`${url}/repos`, data => {
       console.log('fetched user repos');
       console.log(data);
   });
});
1
2
3
4
5
6
7
8

在Promise之前,如果想要确保两个ajax请求的顺序,需要将后面的ajax放到之前ajax请求的回调中(随着回调函数的增加会形成回调地狱)。如上述代码所示:先获取所有的用户信息,然后再根据第一个用户信息来获取这个用户的所有仓库。上述代码通过Promise改下如下:

axios.get('https://api.github.com/users')
   .then(response => {
       const {login, url} = response.data[0];
       return axios.get(`${url}/repos`);
   }).then(response => {
       console.log(response.data);
   });
1
2
3
4
5
6
7

Promise用途

  • Promise主要用于异步计算;
  • 可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果;
  • 可以在对象之间传递和操作Promise,帮助我们处理队列。

Promise将多个异步回调改写成同步形式:

// Promise将异步回调以同步的形式展现出来
Promise.then(() => {
    // 如果返回一个Promise实例。下一个then中回调会受上一个Promise的状态影响。
    return new Promise();
}).then(() => {
    return new Promise();
}).then(() => {
    // 其它操作
});
1
2
3
4
5
6
7
8
9

状态响应函数可以返回新的Promise或其他值。如果返回新的Promise,那么下一级的then()会在新Promise状态改变之后执行。如果返回其它任何值,则会立刻执行下一级then()

Promise产生的原因?

浏览器中的javascript

  1. 异步操作以事件为主。
  2. 回调主要出现在ajaxfile api

node.js

  1. 无阻塞高并发,是Node.js的招牌。
  2. 异步操作是其保障。
  3. 大量操作依赖回调函数。

异步回调有四个问题:

  • 嵌套层次很深,难以维护,最终陷入陷入回调地狱;
  • 无法正常使用return和throw
  • 无法正常检索堆栈信息;
  • 多个回调之间难以建立联系。
new Promise(
	/*执行器 executor*/
	(resolve, reject) => {
		//一段耗时很长的异步操作
		resolve(); //数据处理完成
		reject(); //数据处理出错
	}
).then(() => {
	// 成功,下一步
}, () => {
	// 失败,做相应处理
});
1
2
3
4
5
6
7
8
9
10
11
12

Promise是一个代理对象,它和原先要进行的操作并无关系;Promise通过引入一个回调,避免更多的回调。

Promise对象通过自身的状态,来控制异步操作。Promise有3个状态:

  • pending:异步操作未完成;
  • fulfilled:异步操作成功;
  • rejected:异步操作失败;

需要注意:上面三种状态里面,fulfilled和rejected合在一起称为resolved,这三种的状态的变化途径只有两种。

  1. 从未完成到成功;
  2. 从未完成到失败。

Promise状态发生改变,就会触发.then()里的响应函数处理后续步骤;Promise一旦状态发生变化,就凝固了,不会再有新的状态变化。这也是 Promise这个名字的由来,它的英语意思是“承诺”,一旦承诺成效,就不得再改变了。这也意味着,Promise实例的状态变化只可能发生一次。

因此,Promise 的最终结果只有两种。

  • 异步操作成功,Promise实例传回一个值(value),状态变为fulfilled
  • 异步操作失败,Promise实例抛出一个错误(error),状态变为rejected

需要注意:每一个then都会返回一个新的Promise。

console.log('here we go');
new Promise(resolve => {
    setTimeout(() => {
      resolve('hello');
    }, 2000);
  }).then(value => {
    console.log(value + ' world');
  });
1
2
3
4
5
6
7
8

两步执行:

console.log('here we go');
new Promise(resolve => {
    setTimeout(() => {
    resolve('hello');
    }, 2000);
    }).then(value => {
    console.log(value);
    return new Promise(resolve => {
        setTimeout(() => {
            resolve('world');
        }, 2000);
    });
}).then(value => { //两个then依次执行,value=上一个then的resolve的回调world
   console.log(value + ' world');
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

结果如下:

here we go
hello
world world
1
2
3

Promise在队列中的应用:在任何地方生成了一个promise队列之后,我们可以把它作为一个变量传递到其他地方,如果我们的操作是队列的状态,即先进先出的状态,就可以在后面追加任意多的then,不管之前的队列是完成还是没有完成,队列都会按照顺序完成。

console.log('start');

let promise = new Promise(resolve => {
    setTimeout(() => {
      // 1秒后输出
      console.log('the promise fulfilled');
      resolve('hello');
    }, 1000);
});
setTimeout(() => {
    promise.then(value => {
       console.log(value); // 3秒后输出
    });
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14

结果:

start
the promise fulfilled
hello
1
2
3

promise在then中不返回promise实例对象,将会执行下一步操作,即使返回值为false,false将会作为返回值传递到相应的then函数中。

console.log('here we go');
new Promise(resolve => { // 执行1
    setTimeout(() => {
      resolve('hello');
    }, 2000);
  }).then(value => {
      console.log(value);  // 执行2
      console.log('everyone');
      (function() { // 执行5。1、这段代码中没有返回新的值,下面一行返回的promise实际是在这个函数中返回的,不是在then的响应函数中返回的,then返回的promise实例就没有等待里面的这个promise完成。2、一直在等待执行,等最后的then返回之后,再执行这个函数。3、没有进入promise队列中,但是进程仍然是登它执行完成后才算是完成。
        return new Promise(resolve => { // 自己用自己的回调
          setTimeout(() => {
            console.log('mr');
            resolve('marry');
          }, 2000)
        });
      }());
      return false; // 执行3。1、false会直接传递到下一步,成为下一个then的value
      // 即使这里将return false;注销,默认也会返回Undefined
    })
    .then(value => { // 执行4
      console.log(value + 'world'); // value = false;
    })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

then方法

  • .then()接受两个函数作为参数,分别代表fulfilled和rejected;
  • .then()返回一个新的Promise实例,所以它可以链式调用;
  • 当前面的Promise状态改变时,.then()根据其最终状态,选择特定的状态响应函数执行。
  • 状态响应函数可以返回新的Promise或其他值;
  • 如果返回新的Promise,那么下一级.then()会在新Promise状态改变之后执行;
  • 如果返回其它任何值,则会立刻执行下一级.then()。

.then()的嵌套

因为.then()返回的还是Promise实例,外面的.then()会等里面的.then()执行完再执行。

console.log('start');
new Promise( resolve => {
    console.log('Step 1');
    setTimeout(() => {
        resolve(100);
    }, 1000);
}).then(value => {
        console.log(value);
        return new Promise(resolve => {
            console.log('Step 1-1');
            setTimeout(() => {
                resolve(110);
            }, 1000);
        }).then(value => {
            console.log('Step 1-2');
            return value;
        }).then(value => {
            console.log('Step 1-3');
            return value;
        });
    }).then(value => {
        console.log(value);
        console.log('Step 2');
    });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

执行结果:

start
Step 1
100
Step 1-1
Step 1-2
Step 1-3
110
Step 2
1
2
3
4
5
6
7
8

then中抛出错误

console.log(200);
const p = new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log(a); // Uncaught ReferenceError: a is not defined
    }, 100);
    // reject();
    console.log(100);
});

p.then(() => {
    console.log('成功');
}, () => {
    // 在then中的函数如果抛出异常,或者是报错,浏览器不会报错,而是直接走reject
    console.log('111');
}).then(() => {
    console.log('成功2');
}, () => {
    console.log('失败');
});
console.log(300);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

输出:

200
100
300
ReferenceError: a is not defined
1
2
3
4
console.log(200);
const p = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject();
    }, 100);
    console.log(100);
});

p.then(() => {
    console.log('成功');
}, () => {
    // 在then中的函数如果抛出异常,或者是报错,浏览器不会报错,而是直接走reject
    console.log(a);
}).then(() => {
    console.log('成功2');
}, () => {
    console.log('失败');
});
console.log(300);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

输出:

200
100
300
失败
1
2
3
4

then穿透

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log); // 输出结果:1
1
2
3
4

解析: Promise.resolve 方法的参数如果是一个原始值,或者是一个不具有 then 方法的对象,则 Promise.resolve 方法返回一个新的 Promise 对象,状态为resolved,Promise.resolve 方法的参数,会同时传给回调函数。 then 方法接受的参数是函数,而如果传递的并非是一个函数,它实际上会将其解释为 then(null),这就会导致前一个 Promise 的结果会穿透下面。

错误处理

Promise会自动捕获内部异常,并交给rejected响应函数处理-catch捕获。 推荐使用catch捕获

console.log('here we go');
new Promise( resolve => {
    setTimeout( () => {
        throw new Error('bye');
    }, 2000);
}).then( value => {
    console.log( value + ' world');
}).catch( error => {
    console.log( 'Error:', error.message);
});
1
2
3
4
5
6
7
8
9
10

Promise会自动捕获内部异常,并交给rejected响应函数处理-reject响应捕获:

console.log('here we go');
new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('bye');
    }, 2000);
})
    .then(value => {
        console.log(value + ' world');
    }, error => {
        console.log('Error:', error);
    });
1
2
3
4
5
6
7
8
9
10
11

729fb242d4cb7f7bf73b76bbb21cc190.png

catch和then连用

catch也会返回一个promise实例,如果没有抛出错误,也会是fulfilled状态,会执行后面的then()

console.log('here we go');
new Promise(resolve => {
    setTimeout(() => {
        resolve();
    }, 1000);
})
    .then(() => {
        console.log('start');
        throw new Error('test error');
    })
    .catch(err => {
        console.log('I catch:', err);
        // 下面这一行的注释将引发不同的走向
        // throw new Error('another error');
    })
    .then(() => {
        console.log('arrive here');
    })
    .then(() => {
        console.log('... and here');
    })
    .catch(err => {
        console.log('No, I catch:', err);
    });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

结果:catch中不抛出错误,后面的then()正常执行。

here we go
start
I catch: Error: test error
    at Promise.then (/Users/liujie26/study/FE-study-notes/sources/css-test/promise-test/catch-then.js:9:15)
arrive here
... and here
1
2
3
4
5
6

catch中抛出错误,后面的then()不执行。

here we go
start
I catch: Error: test error
    at Promise.then (/Users/liujie26/study/FE-study-notes/sources/css-test/promise-test/catch-then.js:9:15)
No, I catch: Error: another error
    at Promise.then.catch.err (/Users/liujie26/study/FE-study-notes/sources/css-test/promise-test/catch-then.js:14:15)
1
2
3
4
5
6

强烈建议在所有队列最后都加上.catch(),以避免漏掉错误处理造成意想不到的问题。

doSomething()
  .doAnotherThing()
  .doMoreThing()
  .catch(err => {
    console.log(err);
  })
1
2
3
4
5
6

下面的四种promises的区别是什么?

doSomething().then(function () {
  return doSomethingElse();
});

doSomething().then(function () {
  doSomethingElse();
});

doSomething().then(doSomethingElse());

doSomething().then(doSomethingElse);
1
2
3
4
5
6
7
8
9
10
11

假设doSomethingdoSomethingElse返回的都是一个Promise实例。

// 问题一
doSomething()
    .then(function () {
        return doSomethingElse();
    })
    .then(finalHandler);

// 答案
// doSomething
// |-----------|
//             doSomethingElse(undefined)
//             |------------|
//                          finalHandler(resultOfDoSomethingElse)
//                          |------------|

// 问题二
doSomething()
    .then(function () {
        doSomethingElse();
    })
    .then(finalHandler);

// 答案
// doSomething
// |------------------|
//                    doSomethingElse(undefined)
//                    |------------------|
//                    finalHandler(undefined)
//                    |------------------|


// 问题三
doSomething()
    .then(doSomethingElse())
    .then(finalHandler);

// 答案
// doSomething
// |------------------|
// doSomethingElse(undefined)
// |----------------------------------|
//                    finalHandler(resultOfDoSomething)
//                    |------------------|


// 问题四
doSomething()
    .then(doSomethingElse)
    .then(finalHandler);

// 答案
// doSomething
// |-----------|
//             doSomethingElse(resultOfDoSomething)
//             |------------|
//                         finalHandler(resultOfDoSomethingElse)
//                         |------------------|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57