本文已参与「新人创作礼」活动, 一起开启掘金创作之路。
Promise
Promises 是异步编程的另一种选择,它的工作方式类似于在其他语言中延迟并在将来执行作 业。一个 Promise 指定一些稍后要执行的代码(就像事件与回调函数一样),并且也明确标 示了作业的代码是否执行成功。你能以成功处理或失败处理为基准,将 Promise 串联在一起,让代码更易理解、更易调试
异步编程的背景
js 的单线程是异步的原因
JS 引擎建立在单线程事件循环的概念上。单线程(Single-threaded )意味着同一时刻只能 执行一段代码,与 Java 或 C++ 这种允许同时执行多段不同代码的多线程语言形成了反差。 多段代码可以同时访问或修改状态,维护并保护这些状态就变成了难题,这也是基于多线程 的软件中出现 bug 的常见根源之一
事件循环(event loop )是 JS 引擎的一个内部处理线程,能监视代码的执行并管理作业队列(后面会讲到事件循环)
以往的异步编程模式
早期只支持定义回调函数来表明异步操作的完成,这样往往会嵌套多层回调,也就是俗称的回调地狱
- 异步操作的返回值
- 首先,对于一个异步操作,在他完成后假设会返回一个有用的数据,那么怎么更好的获取并使用这个数据呢?我们可能首先会想到给这个异步操作提供一个回调,用于接收异步操作产生的返回值。
- 其次,假如说这个异步操作,他也可能会失败,那么我们也需要对这个失败的结果做出反应,那么也需要传入一个回调用于处理失败的返回值。
所以:
/**
* @param {*} value 传入的值
* @param {*} success 一个用于成功状态的回调函数
* @param {*} reject 一个失败状态的回调函数
*/
function isString(value, success, reject) {
setTimeout(() => {
if (typeof value === "string") {
success(value);
} else {
reject(value);
}
}, 1000);
}
const onSuccess = (e) => {
console.log(`success:${e}`);
};
const onReject = (e) => {
console.log(`reject:${e}`);
};
// 调用
isString(6, onSuccess, onReject); //reject: 6
如果异步返回值又会依赖于另一个异步操作,那么可想而知这个回调将会非常的恶心、复杂。
Promise 基础
ES6 新增的引用类型 Promise,可以通过 new 操作符来实例化,但是创建一个新的Promise的时候必须传入一个执行器函数作为参数,否则的话会报错SyntaxError
执行器函数(是同步执行的)
Promise 的状态的改变都是私有的,需要在内部进行操作,而内部操作就是在执行器函数中完成,(我的理解就是这个执行器函数里面就是对这个任务对象的描述)执行器函数提供两个函数,通常命名为resolve()以及reject() 调用resolve()能够把状态改为 fullfilled,调用reject()能够把状态改为rejected,
let pro = new Promise((resolve, reject) => {});
console.log(pro); //Promise { <pending> }
任何一个异步场景都可以看作是一个 Promise 对象,一个 Promise 对象也可以看作是一个任务对象
Promise 的生命周期
每创建一个 Promise 就会产生一个任务对象,这个任务对象存在两个阶段三种状态
注意:
一个任务只能未决阶段->已决阶段,反之不可 任务的状态一经确定后续不可以改变
内部的 PromiseState 属性会被设置为 "pending" 、 "fulfilled" 或 "rejected" ,以
反映 Promise 的状态
- 从挂起状态->完成状态,任务成功可能会需要一个
data,这个过程叫ersolve - 从挂起状态->失败状态,任务失败可能会传出一个
reason,这个过程叫regect
Promise API
then()
// 创建一个任务对象,该任务立即进入 pending 状态
const pro = new Promise((resolve, reject) => {
// 任务的具体执行流程,该函数会立即被执行
// 调用 resolve(data),可将任务变为 fulfilled 状态, data 为需要传递的相关数据
// 调用 reject(reason),可将任务变为 rejected 状态,reason 为需要传递的失败原因
});
pro.then(
(data) => {
// onFulfilled 函数,当任务完成后,会自动运行该函数,data为任务完成的相关数据
},
(reason) => {
// onRejected 函数,当任务失败后,会自动运行该函数,reason为任务失败的相关原因
}
);
当 Promisie 状态改变时,允许我们对 Promise 的返回值做出相应的操作
then()方法是 Promise 原型上的方法,任何由 Promise 实例化的 Promise 对象都拥有该方法。
then()任何传递的非函数类型的参数都会被静默忽略
- 第一个参数是 Promise 被完成时要调用的函数,与异步操作关联的任何附加数据都会被传入这个完成函数。
- 第二个参数 则是 Promise 被拒绝时要调用的函数,与完成函数相似,拒绝函数会被传入与拒绝相关联的任何附加数据。
这两个参数都是可选的
同时还提供了方法catch()相当于只给then()函数提供了拒绝的函数
以下代码的执行结果是一样的
let pro = new Promise((resolve, reject) => {
reject("错误");
});
// 以下只捕捉错误信息
pro.then(null, (res) => {
console.log(res); //错误
});
pro.catch((err) => {
console.log(err); //错误
});
创建未决的 Promise
Promise 构造器就是创建未决的 Promise 的最好方式。这个构造器接受一个参数,一个被称为执行器函数的参数
创建已决的 Promise
有两种方法可使用指定值来创建已决的 Promise 。 其实就是在创建之初就已经确定了这个 Promise 的状态是完成状态 或 失败状态
- 使用
Promise.resolve()方法 Promise.resolve() 方法接受单个参数并会返回一个处于完成态的 Promise 。这意味着没有 任何作业调度会发生,并且你需要向 Promise 添加一个或更多的完成处理函数来提取这个参 数值。例如:
let promise = Promise.resolve(42);
promise.then(function (value) {
console.log(value); // 42
});
- 使用
Promise.reject()方法 方法和Promise.resolve()一样,唯一不同的地方就是,创建的 Promise 处于拒绝状态
let promise = Promise.resolve(42);
promise.catch(function (value) {
console.log(value); // 42
});
若你传递一个 Promise 给 Promise.resolve() 或 Promise.reject() 方法,该 Promise 会不作修改原样返回。
Promise 链式调用
-
then 方法必定会返回一个新的 Promise
可理解为
后续处理也是一个任务 -
新任务的状态取决于后续处理:
-
若没有相关的后续处理,新任务的状态和前任务一致,数据为前任务的数据
-
若有后续处理但还未执行,新任务挂起。
-
若后续处理执行了,则根据后续处理的情况确定新任务的状态
- 后续处理执行无错,新任务的状态为完成,数据为后续处理的返回值
- 后续处理执行有错,新任务的状态为失败,数据为异常对象
- 后续执行后返回的是一个任务对象,新任务的状态和数据与该任务对象一致
-
then 方法必定会返回一个新的 Promise
处理场景1:then()方法的后续处理仍然是一个新的任务
let pro1 = new Promise((resolve, reject) => {
console.log("学习");
resolve();
});
let pro2 = pro1.then(() => {
console.log("考试");
});
console.log(pro2);
//学习
//Promise { <pending> }
//考试
新任务的状态取决于后续处理
处理场景2:若没有相关的后续处理,新任务的状态和前任务一致,数据为前任务的数据
let pro1 = new Promise((resolve, reject) => {
console.log("学习");
// 失败了
reject();
});
// 后续处理没有对失败做下一步处理
let pro2 = pro1.then(() => {
console.log("考试");
});
setTimeout(() => {
console.log(pro1, pro2);
}, 100);
//学习
//Promise { <rejected> undefined } Promise { <rejected> undefined }
处理场景3:若有后续处理但还未执行,新任务挂起
let pro1 = new Promise((resolve, reject) => {
console.log("学习");
// 失败了
setTimeout(() => {
reject();
}, 1000);
});
// 后续任务对失败做下一步处理,但是还未执行
let pro2 = pro1.catch(() => {
console.log("复习,补考");
});
setTimeout(() => {
console.log(pro1, pro2);
}, 100);
//学习
//Promise { <pending> } Promise { <pending> }
//复习,补考
若后续处理执行了,则根据后续处理的情况确定新任务的状态
打个比方就是,我们先是学习学习的任务完成了,随后我们考试,考试这个新的任务的状态可能成功可能失败,所以新的任务的状态取决于这个后续处理的情况
处理场景4:后续处理执行无错,新任务的状态为完成,数据为后续处理的返回值
let pro1 = new Promise((resolve, reject) => {
console.log("学习");
resolve(1);
});
let pro2 = pro1.then(() => {
console.log("考试");
return "考试顺利";
});
setTimeout(() => {
console.log(pro2);
}, 100);
//学习
//考试
//Promise { '考试顺利' }
处理场景5:后续处理执行有错,新任务的状态为失败,数据为异常对象
let pro1 = new Promise((resolve, reject) => {
console.log("学习");
resolve(1);
});
// 后续任务对失败做下一步处理,但是还未执行
let pro2 = pro1.then(() => {
console.log("考试");
throw new Error("睡着了");
});
setTimeout(() => {
console.log(pro2);
}, 100);
//学习
//考试
//Promise {<rejected> Error: 睡着了}
处理场景6:后续执行后返回的是一个任务对象,新任务的状态和数据与该任务对象一致
let pro1 = new Promise((resolve, reject) => {
console.log("学习");
resolve(1);
});
let pro2 = pro1.then(() => {
return new Promise((resolve, reject) => {});
});
setTimeout(() => {
console.log(pro2);
}, 100);
//学习
//Promise { <pending> }
由于链式任务的存在,异步代码拥有了更强的表达力
// 常见任务处理代码
/*
* 任务成功后,执行处理1,失败则执行处理2
*/
pro.then(处理1).catch(处理2);
/*
* 任务成功后,依次执行处理1、处理2
*/
pro.then(处理1).then(处理2);
/*
* 任务成功后,依次执行处理1、处理2,若任务失败或前面的处理有错,执行处理3
*/
pro.then(处理1).then(处理2).catch(处理3);
Promise 的静态方法
| 方法名 | 含义 |
|---|---|
| Promise.resolve(data) | 直接返回一个完成状态的任务 |
| Promise.reject(reason) | 直接返回一个拒绝状态的任务 |
| Promise.all(任务数组) | 返回一个任务 任务数组全部成功则成功 任何一个失败则失败(找失败) |
| Promise.any(任务数组) | 返回一个任务 任务数组任一成功则成功(找成功) 任务全部失败则失败 |
| Promise.allSettled(任务数组) | 返回一个任务 任务数组全部已决则成功 该任务不会失败 |
| Promise.race(任务数组) | 返回一个任务 任务数组任一已决则已决,状态和其一致(具体的状态取决于,任务数组中第一个有结果的 Promise) |
消除回调
有了 Promise,异步任务就有了一种统一的处理方式
有了统一的处理方式,ES 官方就可以对其进一步优化
ES7 推出了两个关键字async和await,用于更加优雅的表达 Promise
async
async 关键字用于修饰函数,被它修饰的函数,一定返回 Promise
async function method1() {
return 1; // 该函数的返回值是Promise完成后的数据
}
method1(); // Promise { 1 }
async function method2() {
return Promise.resolve(1); // 若返回的是Promise,则method得到的Promise状态和其一致
}
method2(); // Promise { 1 }
async function method3() {
throw new Error(1); // 若执行过程报错,则任务是rejected
}
method3(); // Promise { <rejected> Error(1) }
await
await关键字表示等待某个 Promise 完成,它必须用于async函数中
async function method() {
const n = await Promise.resolve(1);
console.log(n); // 1
}
// 上面的函数等同于
function method() {
return new Promise((resolve, reject) => {
Promise.resolve(1).then((n) => {
console.log(n);
resolve(1);
});
});
}
await也可以等待其他数据
async function method() {
const n = await 1; // 等同于 await Promise.resolve(1)
}
如果需要针对失败的任务进行处理,可以使用try-catch语法
async function method() {
try {
const n = await Promise.reject(123); // 这句代码将抛出异常
console.log("成功", n);
} catch (err) {
console.log("失败", err);
}
}
method(); // 输出: 失败 123