因为JS语言本身使用场景的问题,其中存在着大量的异步事件处理,比如当你从一个服务器获取图像,通常需要传输过程和处理时间,你不可能马上就得到,于是我们需要设置回调函数,在本地真正获得图像后做一些处理;除了网络请求,JS中还有一些异步事件,比如定时器、事件绑定、requireAnimationFrame等。这些和网络、IO相关的事件不得不让你用异步的方式来编写代码。

然而有时候我们有需要同步的执行一系列的事件,比如C需要依赖B执行完,而B需要依赖A执行完,在其他编程语言中,我们会这样写:

1
2
3
A()
B()
C()

但对于异步的JS,我们可以这样写:

1
2
3
4
5
6
7
function A(args1,callback){ ...; callback() };
function B(args2,callback){ ...; callback() };
function C(){};
A(args1,function B(args2, function C(){
....
}){
})

通过回调函数的方式,当A执行完执行回调函数通知传入的B执行,B执行完通知C执行,这样可以实现同步,但这样实现同步的代码实在过于混乱,倘若我们有十几个同步事件,写出来的代码几乎就没办法维护了。

ES6中提供了类似于其他语言的写法,可以让我们依照次序执行ABC,下面这段代码,是在控制台输出312,其中的各种语法我们会在稍后讲解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var count = 0;
function A() { return new Promise((resolve) => { setTimeout(() => { console.log(1);++count;resolve('OK') }, 1000) }) }
function B() { return new Promise((resolve) => { setTimeout(() => { console.log(2);++count;resolve('OK') }, 2000) }) }
function C() { return new Promise((resolve) => { setTimeout(() => { console.log(3);++count;resolve('OK') }, 3000) }) }


~async function(){
console.time('sync')
await C();
console.log('count is ',count); // 1
await A();
console.log('count is ',count); // 2
await B();
console.log('count is ',count); // 3
console.timeEnd('sync') // sync: 6027.76416015625 ms
}()

认识Promise

本节内容 :

  • 构造函数的方式执行Promise
  • Promise实例状态
  • Executor函数和它的两个方法
  • Promise如何管控异步编程
  • .then/.catch的链式调用
  • Promise调用自己原型上的方法创建实例
  • Promise.then状态和值的分析

ES6的Promise语法,可以让异步函数按照顺序执行。Promise可以按照构造函数的方式执行,也可以调用自己原型上的方法执行,但无论是哪种,生成的Promise实例都有PromiseState和PromiseResult

Promise通过构造函数的方式执行,创建一个Promise实例:

1
2
3
4
5
let executor = function(resolve,reject){
resolve('OK');
reject('Error');
}
let p = new Promise(executor)

new Promise实例需要传入一个executor函数(如果不是函数就报错),executor函数用于管控异步操作,executor也可以是同步函数,里面不一定非要有异步事件

  • executor函数接收两个参数,分别为resolve和reject
  • 执行resolve,将实例的状态变为成功(fulfilled),返回结果为resolve接受的参数(”OK”)
  • 执行reject,将实例的状态变为失败(rejected),返回结果为reject接收的参数(”Error”)
  • 一旦状态从初始的pending发生改变,都无法再次改变其状态
  • 如果executor函数代码执行报错,则实例的状态也会变为失败,PromiseResult是失败的原因

Promise实例的状态

1
2
[[PromiseState]]: "pending"	
[[PromiseResult]]: undefined
  • PromiseState代表当前实例状态,pending是准备状态、fulfilled/resolved是执行成功、rejected是执行失败,用于标识异步任务的执行成功与否
  • PromiseResult代表异步任务的返回结果,默认是undefined,一般存储成功的结果或者是失败的原因

Promise实例的方法:then/catch/….

.then控制状态成功之后做什么、失败之后做什么:

1
2
3
4
5
6
7
8
p.then(res => {
// 当p1的实例状态修改为fulfilled的时候,通知传递的第一个函数执行
// res == PromiseResult
console.log('成功', res)
}, rea => {
//当p1实例的状态修改为rejected的时候,通知第二个参数执行,rea == PromiseResult
console.log("失败", rea)
})

返回的Promise状态决定了新实例的状态

Promise如何管控异步编程的?

  1. new Promise的时候创建一个Promise实例,此时在executor函数中管理了一套异步的代码
  2. 后期等异步操作成功或者失败的时候,执行resolve/reject,以此来控制Promise实例的状态和结果
  3. 根据状态和结果,就可以控制基于.then注入的两个方法中的一个去执行了

Promise 异步代码执行顺序(代码如上)?

  1. new Promise
  2. 执行executor:设置一个异步定时器
  3. 执行p1.then注入两个方法,注入的方法会保存起来
  4. 等待1000ms
  5. 执行定时器回调函数:执行resolve改变promise状态和值
  6. 通知之前基于.then注入的两个方法中的一个执行

[ 1 ] 例 说明Promise执行顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Promsie中是同步的,但是resolve/reject修改Promise实例状态和值后,需要通知.then方法执行,通知的过程是异步的
let p1 = new Promise((resolve, reject) => {
console.log(1);
// 修改状态和值,并且通知基于then注入的方法执行[问题是.then还没有执行,方法还没被注入,不知道该通知谁来执行,所以此时需要把"通知方法执行操作保存起来,放入到等待任务队列中" -> 这个操作本身是异步的]
resolve(2)
console.log(3);
})
p1.then(result => {
console.log("OK", result);
}, reason => {
console.log("Err", reason);
})
console.log(4);
-------
1
3
4
OK 2

Promise调用自己原型上的方法执行,创建一个状态为成功的Promise实例:

1
let p1 = Promise.resolve("OK");

Promise实例状态和值的分析

  1. new Promise出来的Promise实例,由resolve/reject的执行来控制其状态和值;或者executor函数执行失败也可以控制其状态和值 (PromiseStatus / PromiseResult)
  2. 通过执行.then返回的新实例,then注入的方法不论哪个方法执行,只要执行不报错,新实例的状态就是fulfilled,只要执行报错,新实例的状态就是rejected;并且新实例的PromiseResult是方法的返回值[1] ;但是如果方法执行,返回的是一个新的Promise实例,则此实例最后的成功或失败直接决定.then返回实例的成功和失败[2]

[ 3 ] 例 返回的Promise状态决定了新实例的状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let p1 = Promise.resolve(1)
let p2 = p1.then(result => {
console.log("成功->", result)
return Promise.reject(2) // 返回的Promise状态决定了新实例的状态
}, reason => {
console.log("失败->", reason)
});
p2.then(result => {
console.log("成功->", result)
}, reason => {
console.log("失败->", reason)
})
---------
成功-> 1
失败-> 2

对于失败的Promise实例,如果没有编写方法处理其结果,则会在控制台抛出异常[但是不阻碍其余代码执行],下面介绍几种处理Promise实例失败的代码写法:

[1] Promise顺延机制,如果当前.then没有写resolve或者reject方法,但是Promise实例触发了该方法,则会顺延到下面的.then,直到最后,如果都没写,则报错:Uncaught (in promise) NO

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Promise.reject("NO")
.then(result => {
console.log("成功->", result);
} /*,reason => {return Promise.reject(reason)}*/ )
.then(null, reason => {
console.log("失败->", reason);
})

---------
失败-> NO


Promise.resolve("YES")
.then(null, reason => {
console.log("失败->", reason);
})
.then(result => {
console.log("成功->", result);
})
----------
成功-> YES

[ 2 ] 用catch捕获失败

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Promise.reject("NO")
.then(result => {
console.log("成功->", result);
})
.then(result => {
console.log("成功->", result);
})
.then(result => {
console.log("成功->", result);
})
.catch(reason => {
console.log("失败->", reason);
})
/*
真实项目中,在多个then链下,其余then方法基本都存放在成功处理的事情,最后一个then存放失败的,这样不论是第一次或者其中某一次导致Promise实例状态失败,都会顺延到最后一个失败的处理函数上进行处理
.then(null,reaso=>{
console.log("失败->", reason);
})
*/

Promise All

Promise.all接受一个函数列表,函数执行都成功,Promise就是成功的,results是按照之前设定的顺序依次存储每个Promise的结果

只要有一个过程失败,立即结束处理,Promise实例也是失败的,PromiseResult记录谁失败和失败的原因

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const api1() = ()=>{ /* .... */};
const api1() = ()=>{ /* .... */};
const api1() = ()=>{ /* .... */};
const fn = ()=>{ setTimeout(()=>{resolve(100)},1000)};
const A = Promsie.resolve("AAA");


let p = Promise.all([api1(), api2(), api3(), fn(), A]);
p.then(result => {
console.log("成功->", result);
})
.catch(reason=>{
console.log("失败->",reason)
})
1
2
3
4
5
6
7
8
成功-> (5) ["2↵", "1↵", "3↵", 100, "AAA"]
0: "1↵"
1: "2↵"
2: "3↵"
3: 100
4: "AAA"
length: 5
__proto__: Array(0)

Promise Race

Promise.all接受一个函数列表,只要其中某一个函数执行完成,就立刻返回,返回结果为执行完成函数的PromiseResult。但其他的函数还会继续执行,只是PromiseResult不会添加到返回值中

1
2
3
4
5
6
7
8
9
var count = 0;
function A() { return new Promise((resolve) => { setTimeout(() => { console.log(1); ++count; resolve('OK') }, 1000) }) }
function B() { return new Promise((resolve) => { setTimeout(() => { console.log(2); ++count; resolve('OK') }, 2000) }) }
function C() { return new Promise((resolve) => { setTimeout(() => { console.log(3); ++count; resolve('OK') }, 3000) }) }


Promise.race([A(), B(), C()]).then(res => {
console.log(res) // OK
})

Async & Await

Async和Await是编排异步任务执行顺序的另一种写法,这种写法更加符合人类直觉,但其本质上是Promise的语法糖

首先我们使用async关键字,放在函数声明之前,将其成为async functionawait 关键字用于管控异步函数。

有如下代码:

1
2
function hello() { return "Hello" };
hello();

将其变为async函数,异步函数会返回一个Promise,我们可以基于.then获取异步任务的返回值(return的值)

1
2
3
4
5
6
7
8
9
10
11
async function hello() { return "Hello" };
hello()

/*
Promise {<fulfilled>: "Hello"}
__proto__: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: "Hello"
*/

hello().then(res => { console.log(res) }) // "Hello"

Reference

[ 1 ] https://www.cnblogs.com/lvdabao/p/es6-promise-1.html

[ 2 ] https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises

[ 3 ] https://promisesaplus.com/