何为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 istoo 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}); }; letaPromise =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(卦名) { returnajax.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); });虽然涉及到的第一类较多,但思路清晰后,应该不难理解。