定义Promise对象
为了解决地狱式的回调,可以使用 Promise 对象,且代码更优雅,由于 Promise 对象是一个构造函数,因此,必须通过实例化来生成,它的定义格式如下代码:
let p = new Promise(function (resolve, reject) {
// 此处做一个异步的事情
});
在定义格式的代码中,需要说明的几个问题:
- 在实例化中,参数为函数,函数中又有两个用于回调的函数。
- 两个回调函数中,resolve 为异步执行成功时的回调,其参数可以传递执行的结果。
- reject 为异步执行失败时的回调,其参数可以传递失败的错误信息。
使用 resolve 和 reject 方法传递出去的参数被谁接收到了,是以何种方式接收的?接下来说下 then 方法。
Promise对象的then方法
Promise 对象实例化后,可以调用 then 方法获取两个回调函数中的传参值,该方法接收两个回调函数作为参数,第一个参数是必选参数,表示异步成功后执行的 resolve 回调函数,第二个参数是可选参数,表示异步失败后执行的 reject 回调函数,它的调用格式如下:
p.then(
function (v) {},
function (e) {}
);
其中参数 v 值表示 resolve 回调函数中的参数值,e 值表示 reject 回调函数中的参数值,如下列代码所示:
let n = 6;
let p2 = new Promise(function (resolve, reject) {
setTimeout(function () {
if (n > 5) {
resolve(n);
} else {
reject("必须大于5");
}
});
});
p2.then(
function (v) {
console.log(v);
},
function (e) {
console.log(e);
}
);
// 执行代码后,由于 n 值大于 5 ,因此,在控制台中输出数字 6 。
此外,一个 then 方法被执行后,如果仍然返回一个 Promise 对象,则可以继续再执行 then 方法,形成链式写法效果,代码如下所示:
p1.then(function (v) {
return p1;
}).then(function (v) {
return p1;
});
Promise对象中的方法
Promise.all方法
日常开发过程中,往往会遇到这种问题,当首次加载某个页面时,由于数据庞大需要分别同时发送多个异步请求向服务器获取数据,最终所有数据返回之后做下一步操作(如“隐藏页面的加载 loading 动画”)。由于很难捕获这些异步请求全部成功的时机,导致这个需求实现起来相当困难。难道就没有解决办法了吗?🤔 这时使用 Promise.all 方法就可以解决这种问题。
使用格式
Promise.all 方法中的参数是一个数组,数组中的每个元素是实例化后的 Promise 对象,格式如下代码:
Promise.all([p1,p2,p3,...]).then(res=>{
// 所有请求成功后的操作步骤
},error=>{
// 某一个请求失败后的操作步骤
});
实践应用
下列通过一个实战来演示 Promise.all 方法的使用过程,功能说明:
- 定义一个函数 p1,返回一个 Promise 对象,在返回过程中,执行一个延时操作,定义一个参数 n ,如果参数 n 大于 0
,则返回该数据,否则,则返回 “ 不能小于 0“ 的字符信息。 - 调用 Promise.all 方法,使用不同的参数,调用三次 p1 函数,当全部执行成功或有一个执行失败后,分别查看控制台的输出信息。
为了实现这个功能,首先,打开我们的线上环境,新建一个 index3.html 文件,再使用快捷键方式生成模版,并在 body 元素中添加 script 元素,如下代码所示:
function p1(n) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
if (n > 0) {
resolve(n);
} else {
reject("不能小于0");
}
}, 1000);
});
}
先传入三个执行成功的任务,在新建页面中的 script 元素中添加如下代码:
Promise.all([p1(5), p1(6), p1(7)]).then(
function (v) {
console.log(v);
},
function (e) {
console.log(e);
}
);
上述代码执行后的效果如下图所示:
从上述效果可以看出,如全部任务执行成功,则将各个执行结果保存在数组中,可以通过 then 方法中的成功回调函数返回。
此外,传入一个执行失败的任务,二个执行成功的任务,在新建页面中的 script 元素中再添加如下代码,
Promise.all([p1(5), p1(-2), p1(7)]).then(
function (v) {
console.log(v);
},
function (e) {
console.log(e);
}
);
上述代码执行后的效果如下图所示:
从上述效果可以看出,如有一个任务执行失败,则通过 then 方法中的失败回调函数返回错误信息。
Promise.race方法
与 Promise.all 方法不同,Promise.race 方法是多个 Promise 实例化对象在比赛, 执行最快的那个任务的结果,将返回给 then 方法中的对应回调函数中,通过这种方式,可以检测页面中某个请求是否超时,并输出相关的提示信息。
使用格式
与 Promise.all 方法一样,Promise.race 中的参数也是一个数组,每个元素也是实例化后的 Promise 对象,格式如下代码:
Promise.race([p1,p2,p3,...]).then(
function(v){
//获取最快任务成功时的返回值
},
function(){
//获取最快任务失败时的返回值
}
)
实战应用
下列通过一个实战来演示 Promise.race 方法的使用过程,功能说明:
- 定义一个模拟异步请求的函数,返回一个 Promise 对象,在返回过程中,执行一个延时 3 秒的操作,请求成功后,则返回一个”请求成功“
的字样。 - 再定义一个超时请求的函数,返回一个 Promise 对象,在返回过程中,执行一个延时 5 秒的操作,如果超过 5
秒,则返回一个”请求超时“ 的字样。 - 调用 Promise.race 方法,添加这 2 个 Promise 对象,当请求大于 5 秒和小于 5
秒时,分别查看控制台的输出信息。
先定义一个延时小于 5 秒的任务,在新建页面中的 script 元素中添加如下代码:
function loadData() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve("请求成功");
}, 3000);
});
}
然后,定义一个延时等于5秒的任务:
function timeOut() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
reject("请求超时");
}, 5000);
});
}
接下来调用 Promise.race() 方法,获取并在显示执行最快任务的返回内容,在新建页面中的 script 元素中添加如下代码:
Promise.race([loadData(), timeOut()]).then(
function (d) {
console.log(d);
},
function (e) {
console.log(e);
}
);
上述代码执行后的效果如下图所示:
由于 loadData 函数的延时时间小于请求超时的延时时间,因此,该任务执行最快,所以在控制台显示 ”请求成功“ 的信息。
如果将 loadData 函数的延时时间修改为 6 秒,即将 loadData 函数的代码修改为如下代码:
function loadData() {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve("请求成功");
}, 6000);
});
}
其他代码不变,页面执行后的效果如下图所示:
由于 timeOut 函数的延时时间小于请求超时的延时时间,因此,该任务执行最快,所以在控制台显示 ”请求超时“ 的信息。
async关键字
async 英文单词的意思是异步,虽然它是 ES7 中新增加的一个关键字,但它的本质是一种语法糖写法(语法糖是一种简化后的代码写化,它能方便程序员的代码开发),async 通常写在一个函数的前面,表示这是一个异步请求的函数,将返回一个 Promise 对象,并可以通过 then 方法取到函数中的返回值,下面通过一个简单示例来说明它的使用。
实例:
async function fn() {
return "12345";
}
fn().then((val) => {
console.log(val);
});
在上述代码中,定义一个名称为 fn 的函数,但由于在函数前添加了关键字 async ,使这个函数将返回一个 Promise 对象,因此,函数执行后,可以直接调用 then 方法;同时,fn 函数中的返回值,就是 then 方法中,执行成功回调函数时的参数值,因此,执行上述代码后,将在页面的控制台输出 “12345” 字符,效果如下所示:
通过上述示例,我们明确以下两点:
- 使用 async 关键字定义的函数,将会返回一个 Promise 对象。
- 函数中有返回值,则相当于执行了 Promise.resolve(返回值) 函数,没有返回值,则相当于执行了
Promise.resolve() 函数。
虽然 async 关键字简化了我们之前实现异步请求中返回 Promise 实例对象的那一步,直接返回了一个 Promise 对象,但是仍然需要在 then 方法中处理异步获取到的数据。有没有什么办法可以继续优化呢?比如省去 then 方法的调用,让异步操作写起来更像同步操作那么简洁明了?答案就是—— await ,接下来我们来介绍下它的用法。
await关键字
await 可以理解为 async wait 的简写,表示等待异步执行完成,await 必须在 async 定义的函数中,不能单独使用,await 后可以返回任意的表达式,如果是正常内容,则直接执行,如果是异步请求,必须等待请求完成后,才会执行下面的代码,来看下列代码。
// 函数 p 返回的是一个 Promise 对象,在对象中,延时 2 秒,执行成功回调函数,相当于模拟一次异步请求
function p(v) {
return new Promise(function (resolve) {
setTimeout(function () {
// 在 p 函数执行时,将函数的实参值 v ,作为执行成功回调函数的返回值。
resolve(v);
}, 2000);
});
}
// 一个用于正常输出内容的函数
function log() {
console.log("2.正在操作");
}
async function fn() {
console.log("1.开始");
await log();
let p1 = await p("3.异步请求");
console.log(p1);
console.log("4.结束");
}
fn();
执行上述代码后,页面在控制台输出的效果如下所示:
根据页面效果,源代码解析如下:
- fn 函数执行后,首先,会按照代码执行流程,先输出“1.开始”。
- 其次,对于没有异步请求的内容,在 await 后面都将会正常输出,因此,再输出“2.正在操作”。
- 如果 await 后面是异步请求,那么,必须等待请求完成并获取结果后,才会向下执行。
- 根据上述分析,由于 方法 p 是一个异步请求,因此,必须等待它执行完成后,并将返回值赋给变量 p1,再执行向下代码。
- 所以,最后的执行顺序是,先输出 “3.异步请求”,再输出 “4.结束”,在 async 函数中的执行顺序,如下图所示。