网站首页 > 基础教程 正文
何为Promise
Promise是一个用于异步执行的对象。它可以在当前的工作成功后,使用then方法来执行成功后的下一步工作。它可以用来取代原来的回调函数。下面是传统的异步工作机制:
function loadFile(successfulFunc, failedFunc) {
// load data
let data = ...;
if (successful) {
successfulFunc(data);
} else {
failedFunc(errMsg);
}
}
两个回调函数作为参数传入。这样可能会导致嵌套的代码过长。而Promise可以编写下面的代码:
let a = new Promise(...);
a.then((value) => {
trim(value);
}).then((newValue) => {
print(newValue);
}).catch((errMsg) => {
console.error(errMsg);
});
then方法是一个回调函数,由JavaScript将上一步成功处理后的数据作为参数自动传入。在第二步中,如果trim函数也返回一个Promise对象,则可以继续串联下去。看起来简洁而有序。
而如果在串联过程中出现任何错误,则可使用catch语句来捕获它。
Promise如何向下传递上一步的数据
从上面可以看出,Promise只关心两种情况:成功或失败。
如果成功,则返回一个回调函数,并将成功加工的数据作为参数向该回调函数传入。then方法,其参数就是这个回调函数。由于该函数已经注入了成功加工的数据,因此我们可以直接使用该数据。
如果失败,则抛出一个错误,并将失败原因为参数向该回调函数传入。用户可以使用catch方法进行捕获。
如何算成功,如何算失败,当然是业务逻辑的问题,应由用户自主定义。Promise只关心:
1、如果成功,数据是什么?
2、如果失败,出错信息是什么?
因此,Promise的构造函数只有一个参数,该参数是一个函数。这个函数,又带有两个回调函数作为其参数,分别负责接收成功的数据与失败的错误信息。
let paramFunc = function(resolveFunc, rejectFunc) {
...
};
let aPromise = new Promise(paramFunc);
现在,假设我们要随机生成一个[1, 100]范围内的随机数,如果在[51, 100]的范围内,则视为成功;否则,视为失败。
function getRandomIntInclusive(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1) + min);
}
let paramFunc = function(resolveFunc, rejectFunc) {
let randNum = getRandomIntInclusive(1, 100);
if (randNum >= 51 && randNum <=100) {
resolveFunc(randNum);
} else {
rejectFunc('Number is too small');
}
};
let aPromise = new Promise(paramFunc);
生成一个随机数,如果在[51, 100]范围内,则视为成功,则以该数作为参数调用resolveFunc方法。resolveFunc方法将作为参数传递给then方法。
如果随机数落在[1, 50]范围内,则视为失败,则以字符串Number is too small作为参数调用rejectFunc方法。rejectFunc方法将作为参数传递给catch方法。
此时运行程序,将出现错误:
Uncaught (in promise) Number is too small
这属于“未捕获异常”的错误。
因为我们调用了rejectFunc回调函数,且一旦随机数小于等于50时,将触发此函数。此函数实际上是一个抛出异常的函数,该函数以我们所提供的错误信息作为参数构造一个Error对象并抛出此异常。故需要用户予以捕获。综上,下面的代码:
aPromise
.then(
(value) => {console.log(value);}
)
.catch(
(errMsg) => {console.error(errMsg);}
);
将使得程序正常运行。
注意,Promise的catch也是一个回调方法,而不是一个语句。
只有resolveFunc的Promise
如果我们不需要抛出异常,则如下所示,Promise的构造方法的参数paramFunc的参数可以只带有一个resolveFunc函数。
function getRandomIntInclusive(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1) + min);
}
let paramFunc = function(resolveFunc) {
let randNum = getRandomIntInclusive(1, 100);
let count = 1;
while(randNum <= 50) {
console.log(`${count++}. small number: ${randNum}`);
randNum = getRandomIntInclusive(1, 100);
}
resolveFunc({count: count, number: randNum});
};
let aPromise = new Promise(paramFunc);
aPromise
.then((value) => {
console.log(`${value.count}. big number: ${value.number}`);
});
在上面的代码中,如果生成了一个小数,与其抛出异常,不如直接重新生成一个随机数,直至该随机数为大数为止。然后,我们以一个对象{count: count, number: randNum}的形式作为参数传给resolveFunc。而在then方法中,对其进行解包即可。
多次运行这个例子,有时可能一次就能得到大数;而有时可能有好几次都是小数,最后一次才是大数。
1. small number: 45
2. small number: 27
3. small number: 33
4. small number: 12
5. small number: 27
6. big number : 59
这个例子也演示了Promise的真实本质:它能够以异步的方式,不知疲倦地干着许多见不得人的脏活、累活,当这些工作都完成后,它才通过then方法向我们一次性地递交最终的成果。
查看Promise的状态
Promise是有状态的,开始时其状态为pending;如果成功,其状态为resolved;如果失败,其状态为rejected。但,我们不能直接使用下面的代码来查看其状态:
console.log(aPromise.status); // undefined
只能查看aPromise的全部内部状态:
console.log(aPromise); // all inner states
下面的代码可查看其全部内部状态的细节:
let aPromise = new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 5 * 1000);
});
let isJobDone = false;
function showStatus(aPromise) {
console.log(aPromise);
if (isJobDone) {
clearInterval(tickId);
}
}
let tickId = setInterval(() => showStatus(aPromise), 1 * 1000);
aPromise.then(() => {
console.log('Time up, Promise settled');
isJobDone = true;
});
首先,在创建aPromise对象时,令其5秒后完成工作。
其次,每隔1秒,查看其状态。
第三,在工作完成后,最后一次查看其状态,并取消定时器。
运行代码,显示:
Promise {status: "pending"}
Promise {status: "pending"}
Promise {status: "pending"}
Promise {status: "pending"}
Time up, Promise settled
Promise {status: "resolved", result: "well done!"}
需注意的是,在Promise内部,使用属性名称result来存储resolve的参数值。
虽然只能看而不能调用,但至少可以帮助我们了解Promise的内部细节,以加深对其了解。
多个Promises的并发控制
Promise有几个静态方法,可以有效地管理多个Promises如何协同工作。
先看all方法。此方法在所有promises对象都成功,或只要有一个失败时,就会触发。
let promise1 = new Promise((resolve) => {
setTimeout(() => {
resolve(`First job done`);
}, 1 * 1000);
});
let promise2 = new Promise((resolve) => {
setTimeout(() => {
resolve(`Second job done`);
}, 2 * 1000);
});
let promise3 = new Promise((resolve) => {
setTimeout(() => {
resolve(`Third job done`);
}, 3 * 1000);
});
Promise.all([promise1, promise2, promise3])
.then((values) => {
console.log(values); // ["First job done", "Second job done", "Third job done"]
});
共有3个promises对象,分别在1秒、2秒、3秒后完成各自的工作。上面的代码将在3秒后,将所有工作成果都打包进一个数组中并打印出来。类似于接力赛跑,3个选手都跑到终点线后,方可打印成绩。而如果期间有任何一个选手出局,也会立即打印该队的成绩。
allSettled方法则不管成败,必须等待他们每个人的最后结果。
any方法则不管成败,只要有一个完成了工作,或所有人都出局,就会触发。
race方法则当一人成功则触发成功,或当一人出局,就会触发出局。
将Ajax转换为Promise
import {ajax} from '/js/esm/ajax.js';
function ajaxLoadTextAsPromise() {
return new Promise((resolve) => {
ajax.loadText('/test.txt', (data) => {
resolve(data);
});
});
}
ajaxLoadTextAsPromise().then((data) => {
console.log(data);
});
要旨:
1、最外层返回一个Promise对象。
2、在Promise对象内部,调用resolve方法传递AJAX所获得的数据。
神奇的resolve静态方法
在上面,我们使用这样的代码来创建一个Promise对象:
let a = new Promise((resolve) => {
resolve(5);
});
这样的代码还不够简练。我们可以编写等效的代码如下:
let a = Promise.resolve(5);
console.log(a); // Promise {<fullfilled>: 1}
Promise的静态方法resolve直接创建并返回一个Promise对象,并且直接为then回调函数传入参数值5。因此,我们可以直接调用a的then方法:
a.then(value => {
console.log(value);
});
用这种方法创建并返回一个Promise对象,非常方便。
串联两个以上的Promise
let a = Promise.resolve(1);
let b = a.then(value => {
value += 1;
return Promise.resolve(value);
});
console.log(a); // Promise {<fullfilled>: 1}
console.log(b); // Promise {<pending>}
变量a在调用then方法时,在其内部,对数值加1,再返回一个新的Promise对象。
此时查看两个对象的状态,变量b由于未调用then方法,因此还属于pending状态。
b.then(value => {
console.log(value); // 2
});
console.log(b); // Promise {<pending>}
上面的代码引入了另一个新的变量用以存储所返回的Promise对象。但更直接的,我们可以不引入新对象,直接串联:
let a = Promise.resolve(1);
a.then(value => {
value += 1;
return Promise.resolve(value);
}).then(value => {
console.log(value); // 2
});
console.log(a); // Promise {<fullfilled>: 1}
因此,如果需要串联,只需在then方法中返还一个新的Promise对象即可。
一个稍微复杂的例子
结合上面几节的内容,我们现在可以编写一个实用的用例代码。
static load六十四卦Text(卦名) {
return ajax.loadTextAsPromise('/data/txt/六十四卦.txt')
.then(text => {
let regexp = new RegExp(`(${卦名}(卦.{1,3})\\n(.+\\n){16}(用.+\\n.+\\n)?)`, 'g');
let resultArr;
let 六十四卦Text;
while((resultArr = regexp.exec(text)) !== null) {
六十四卦Text = resultArr[0];
}
return Promise.resolve(六十四卦Text);
});
}
此例中,loadTextAsPromise返回一个Promise对象。虽是文本文件,但所有六十四卦的内容都在里面,故需要根据特定的卦名取出相应的卦即可。上面代码使用正则表达式来快速取出符合条件的卦。由于需要对数据进行进一步加工,故在then方法中又再次返回一个Promise对象。
这样,在客户端就可以使用这样的代码:
MhysTextLoader.load六十四卦Text('大有').then(data => {
console.log(data);
});
虽然涉及到的对象较多,但思路清晰后,应该不难理解。
猜你喜欢
- 2024-11-05 javascript的科普基础二 javascript的介绍
- 2024-11-05 JavaScript-第二章 javascriptj
- 2024-11-05 Javascript一些实用技巧 javascript循环技巧
- 2024-11-05 第31节 类型和对象-Javascript-零点程序员-王唯
- 2024-11-05 Js复习小结 js总结
- 2024-11-05 「收藏」JS数组排序技巧汇总(冒泡、sort、快速、希尔等排序)
- 2024-11-05 web前端:原生js全动画企业官网,开机动画、切屏/分屏动画
- 2024-11-05 SpreadJS教程:如何在填报场景中使用数据绑定获取数据源
- 2024-11-05 纯JavaScript实现的MQTT智能门锁 智能门锁近三年的市场数据采集
- 2024-11-05 详解Javascript中被你忽略的浮点数运算的坑,来学习吧
- 最近发表
- 标签列表
-
- jsp (69)
- gitpush (78)
- gitreset (66)
- python字典 (67)
- dockercp (63)
- gitclone命令 (63)
- dockersave (62)
- linux命令大全 (65)
- pythonif (86)
- location.href (69)
- dockerexec (65)
- tail-f (79)
- queryselectorall (63)
- location.search (79)
- bootstrap教程 (74)
- 单例 (62)
- linuxgzip (68)
- 字符串连接 (73)
- html标签 (69)
- c++初始化列表 (64)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)