概述

Promise,要我解释它是个什么东西,我一时间还真的不能好好组织语言,在我的理解里,Promise,字面意思他是一个承诺,而在JavaScript里面他是一种异步操作、任务的解决方案。

同步与异步的理解

同步是一种阻塞的任务,异步是非阻塞式的任务

异步例子:

  • 早上起来赶着去上课,买了早饭,可以一边赶路一边吃。

同步例子:

  • 早上起来赶着去上课,买了早饭,我们得先赶去教室再吃早饭,也可以先吃早饭再赶去教室。

同步强调赶路和吃早饭这两个任务不能同时进行,在同一时间,要么吃早饭要么赶路。不管是同步还有异步,都是合理的。因为最终都指向了一个结果:到教室上课

显然同步的方式会产生额外的时间消耗,在计算机编程语言里面,显然是不合理的。在计算机的世界中,这样发生阻塞是非常致命的。通常会导致渲染线程(
主线程)阻塞。
界面卡在某一个地方。对于异步任务的执行,现如今使用的最多的方案是多线程和异步。

在JavaScript中使用的是异步的方式进行解决。

Promise

在JavaScript里面,Promise描述了一个异步操作类,他在编程语言里被抽象成一个类,每个实例化的Promise就代表具体的异步操作任务实例。
既然是一个类,我们就可以使用new关键字去实例化一个Promise对象。

1
const promise = new Promise() //必须传递一个参数,这行代码运行会报错

如何描述我们的异步任务?

Promise的构造函数中需要传递一个函数,这个函数中就描述了我们需要进行的异步操作,比如:

1
2
3
const promise = new Promise((resolve, reject) => {
console.log("描述的异步操作")
})

可以看到这个函数也是可以接收参数的,而这两个参数也是函数类型的参数。分别用于表示异步操作完成了、异步操作失败了。
Promise既然是用来描述一个异步任务的,那么这个异步任务的执行肯定会存在一个状态的,比如:正在执行中、解决了、失败了。无非3种状态。
正在执行中属于pending未决状态,解决了resolve、失败了reject属于结果态,pending一旦变化成为了结果态的某一种,就无非对状态进行变更,结果态中的成功和失败也不允许进行状态变更。

上文的resolve和reject函数就是用于变更状态的,如果在函数中没有调用resolve或者reject,那我们的promise会一直处于一个pending的状态

1
2
3
4
5
6
7
8
9
10
11
const promise = new Promise((resolve, reject) => {
console.log()
})
setInterval(() => {
console.log(promise)
}, 1000)
// Promise { <pending> }
// Promise { <pending> }
// Promise { <pending> }
// Promise { <pending> }
// ...

当我们调用了resolve或者reject,promise的状态会立马进行变更。变成结果态,是成功或失败,完全取决于我们在函数里面的逻辑。

举个例子,现在需要写一个函数,通过一个url去动态生成一个img标签,并且当图片加载完成的时候,返回成功,并且在回调里面传输img标签的数据,当失败的时候就传递错误的原因即可。
图片加载,它肯定是一个耗时的操作,我们可以使用Promise对象来对其进行描述。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function createImage(url) {
return new Promise((resolve, reject) => {
//创建img标签
const img = document.createElement('img')
//将img的src属性设置为url
img.src = url
//将图片加载到文档流当中
document.body.appendChild(img)
//当图片加载完成的时候执行resolve
img.onload = () => {
resolve(img) //传递加载完成的img标签
}
//当图片加载失败的时候执行reject
img.onerror = (event) => {
reject(event) //图片加载失败的时候调用reject表示任务失败
}
})
}

回调处理

任务是描述完了,实际开发中我们很少去关注这个任务目前是处于哪一个状态,更多关心的是:这个任务成功的时候我们干嘛,或者出现错误了我们要怎么处理。

比如网络请求。

1
2
3
fetch("https://www.xxx.com/api/getData").then((res) => {/*处理成功后的回调,比如渲染数据到页面上,执行相关的数据清洗操作*/
}, (err) => {/*发生错误了做什么?*/
})

还可以这样写

1
2
3
4
fetch("https://www.xxx.com/api/getData").then((res) => {/*处理成功后的回调,比如渲染数据到页面上,执行相关的数据清洗操作*/
}).catch((reason) => {
// 发生错误了做什么?
})

两种写法都可以,第二种写法更加的语义化。

Promise链

实际情况下,我们不可能只做一个异步任务,并且当某个异步任务完成之后,这个异步任务的结果会作为新任务的条件继续去执行新的任务,所以这里就会存在一种任务链的关系。
比如我们学习、考试、上大学
学习学好了才能够在考试中如鱼得水。考试考好了我们就上大学。
在JavaScript里面如何用Promise描述这个过程呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function study() {
return new Promise((resolve, reject) => {
console.log("好好学习天天向上!")
setTimeout(() => {
console.log("学习完了,我要去考试了")
if (Math.random() > 0.5) {
//学习好了
resolve('恭喜你,你是高考状元')
} else {
reject('没有好好学习,没考好')
}
}, 1000)
//用随机数来表示我们考试是否考好了

})
}

study().then((value) => {
console.log(value) //恭喜你,你是高考状元
}, (reason) => {
console.log(reason) //没有好好学习,没考好
})

promise链的关系如下图

相关理解:

  1. then方法必定会返回一个新的Promise对象,理解为后续处理也是一个任务
  2. 新Promise对象的状态取决于后续的处理
    • 如果没有相关的后续操作,新Promise的状态和前一个Promise的状态是一致的,数据也和前一个Promise的数据一致。
    • 如果又后续的处理但是还没有执行,新Promise对象的状态是pending状态
    • 如果后续处理执行了,则会根据后续处理的情况确定新Promise的状态
      • 后续处理执行没有错误,新Promise的状态会变更未fulfilled,数据为后续处理的返回值
      • 后续处理执行存在错误,新Promise的状态会变更为rejected,数据为异常对象
      • 后续执行后返回的是一个Promise对象,则新Promise的状态于这个返回的Promise对象的状态是一致的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const promise1 = new Promise((resolve) => {
resolve(1)
})

const promise2 = promise1.then((res) => {
console.log(res)
return 2
})
const promise3 = promise2.then((res) => {
console.log(res)
})
console.log(promise1, promise2, promise3)
setTimeout(() => {
console.log(promise1, promise2, promise3)
}, 500)

// Promise { 1 } Promise { <pending> } Promise { <pending> }
// 1
// 2
// Promise { 1 } Promise { 2 } Promise { undefined }

代码执行,promise1构造函数执行,promise1直接进入了一个fulfilled的状态,并且数据是1
promise2、promise3的状态依赖promise1的回调函数的执行,这个时候回调并未执行,而是放到事件队列当中
主线程空闲,promise1的回调被执行,并且没有错误,打印了数据1,返回了数据2,promise2的回调被执行,打印了数据2,并且没有错误,返回了undefined

静态方法

方法名 含义
Promise.resolve(data) 直接返回一个完成状态的Promise
Promise.reject(reason) 直接返回一个拒绝状态的Promise
Promise.all(Promise对象数组) 返回一个Promise,Promise对象数组所有的Promise全部成功则成功,有一个失败就会失败
Promise.any(Promise对象数组) 返回一个Promise,Promise对象数组任意一个Promise对象成功则成功,全部失败则失败
Promise.allSettled(Promise对象数组) 返回一个Promise,Promise对象数组所有Promise对象都是已决状态则成功,这个Promise对象不会失败
Promise.race(Promise对象数组) 返回一个Promise,Promise对象数组任意一个Promise对象已决状态则已决,状态和这个已决的Promise对象的状态一致

async和await语法糖

该语法糖在es7的时候提出,主要是为了消除Promise带来的回调写法,更加优雅的表达代码

在没有async和await之前,我们是这样写代码的

1
2
3
4
5
6

fetch("http://localhost:8080/api/student").then(data => {
// 对获取到的数据进行处理
}).catch(err => {
// 对出现的错误进行处理
})

有了语法糖,我们可以更好的表示我们的代码,不再产生回调的写法,书写的代码更符合人的逻辑思维习惯

1
2
3
4
5
6
7
8
(async () => {
try {
const data = await fetch("http://localhost:8080/api/student") //调用api获取数据
//后续处理逻辑
} catch (e) {
//如果fetch出现了异常,在这里处理错误
}
})();

async必须作用于一个函数,表明了这个函数返回的一定是一个Promise对象,await表示等待这个Promise完成,也就是等待这个Promise从pending的状态变成fulfilled状态