异步事件管理之Promise详解

2022-12-18 0 495

晚期的应用程序端触发器程式设计主要倚靠反弹表达式、该事件窃听、正式发布/订户、Promise的各种类型polyfill这三种形式展开,ES6面世后在完备与此同时实现了Promise规范化的与此同时,还导入了Generator表达式或其派生的async/await句法糖,将JavaScript触发器程式设计带进了崭新期。而随即问世的rxjs则更进一步明显改善了JavaScript触发器程式设计的新体验。

异步事件管理之Promise详解

本栏方案是特别针对Web应用程序端触发器程式设计编写3首诗,依次依照触发器该事件管理工作控制技术发展的天数线,以ES6 Promise规范化做为终点,逐渐带出async/await,最终深入探讨RxJS的采用,并对二者间采用上的优劣展开了纵向的较为。

责任编辑编写操作过程中,所致严谨性各方面的权衡,Promise API各方面的文本译者自《Mozilla Promise Reference》,部份有关Promise状况的截屏提及自《JavaScript PromiseMini书》。书名最先刊登在本栏的Github Page,须要写作记事本的老师可以间接出访网志书名:

由于前段天数一两年的在展开PCB与电脑系统各方面的科学研究,async/await和Rxjs部份的文本仍然处在纳扎雷的状况,中后期将以系列产品该文的形式相继正式发布到chan。

热烈欢迎我们高度关注社会公众号【IT圈】:Electronics, Embedded & Web

异步事件管理之Promise详解

ES6的Promise

ES6 Promises规范化源于于开放源码街道社区的Promises/A+,是应用程序JavaScript以及NodeJS上较为晚期的触发器该事件处理方案,也是目前较常采用的触发器处理机制。

Promise的3种状况

ES6 Promises采用一个Promise对象来代表一个触发器操作,它可以包含如下三种状况:

fulfilled:操作成功,then()方法的onFulfilled表达式被调用。rejected:操作失败,then()方法的onRejected表达式被调用。pending:初始状况,可能触发fulfilled或rejected状况中的一种。

pending状况的Promise对象可能触发fulfilled状况并传递一个值给相应的状况处理方法,也可能触发失败状况rejected并传递一个失败信息。当其中任意一种情况出现时,Promise对象then方法所绑定的处理表达式(onfulfilled()和onrejected())就会被调用。当Promise状况为fulfilled时调用onfulfilled(),当Promise状况为rejected时调用onrejected(),三种状况间的转换图如下:

异步事件管理之Promise详解
Promise.prototype.then()和Promise.prototype.catch()方法会返回一个崭新的Promise对象,因此它们间可以展开链式调用。

可以通过new关键字调用构造表达式Promise()去实例化一个Promise触发器对象。

const promise = new Promise((resolve, reject) => { // … … });

Promise示例代码

一个简单易于理解的采用Promise的示例如下:

function asyncFunction() { return new Promise((resolve, reject) => { setTimeout(() => { resolve(“你好,触发器操作!”); }, 5000); }); } asyncFunction() .then(function(value) { console.info(value); // 打印”你好,触发器操作!” }) .catch(function(error) { console.error(error); // 打印错误信息 });
function getURL(URL) { return new Promise((resolve, reject) => { let xhr = new XMLHttpRequest(); xhr.open(GET, URL, true); xhr.onload = function() { if (xhr.status === 200) { resolve(xhr.responseText); } else { reject(new Error(xhr.statusText)); } }; xhr.onerror = function() { reject(new Error(xhr.statusText)); }; xhr.send(); }); } const URL = “http://www.sina.cn/demo”; getURL(URL) .then(function onFulfilled(value) { console.info(value); }) .catch(function onRejected(error) { console.error(error); });

Promise构造表达式上的原型

Promise.prototype.constructor

表示Promise构造表达式的原型,以便于在原型上放置then()、catch()、finally()方法。

let promise = new Promise((resolve, reject) => { // resolve() or reject() });

Promise.prototype.then(onFulfilled, onRejected)

then()方法返回一个Promise,两个参数依次是Promise成功或者失败状况的反弹表达式。如果忽略某个状况的反弹表达式参数,或者提供非表达式参数,then()方法将会丢失该状况的反弹信息,但是并不会产生错误。如果调用then()的Promise的状况发生改变,但是then()中并没有对应状况的反弹表达式,则then()最终将创建一个未经反弹表达式处理的崭新Promise对象,并采用最初的Promise状况来做为这个崭新Promise的状况。

let promise = new Promise((resolve, reject) => { resolve(“传递给then内的value值。”); }); promise.then( value => { console.info(value); // 打印“传递给then内的value值。” }, error => { console.error(error); } );

Promise.prototype.catch(onRejected)

catch()只处理rejected的情况,该方法返回一个Promise,实质是Promise.prototype.then(undefined, onRejected)的句法糖。

let promise = new Promise((resolve, reject) => { resolve(“传递给then内的value值。”); }); promise .then( value => { console.info(value); // 打印“传递给then内的value值。” }, error => { console.error(error); } );

Promise.prototype.finally(onFinally)

finally()方法返回一个Promise,在then()和catch()执行完成后,都会调用finally指定的反弹表达式,这样可以避免一些重复的语句与此同时出现在then()和catch()当中。

let promise = new Promise((resolve, reject) => { resolve(“传递给then内的value值。”); reject(“传递给catch内error的值。”); // 此处reject并未被执行,因为Promise状况已经在上面一条语句resolve。 }); promise .then(value => { console.info(value); }) .catch(error => { console.error(error); }) .finally(() => { console.log(“执行到finally方法。”); }) /* 打印结果 */ // 传递给then内的value值。 // 执行到finally方法。
包括Eage在内的IE系列产品应用程序目前都不支持finally()方法。

Promise对象实例方法

Promise.resolve(value)

该方法会根据参数的不同,返回不同的Promise对象。当接收Promise对象做为参数的时候,返回的还是接收到的Promise对象;

let jqueryPromise = $.ajax(“http://gc.ditu.aliyun.com/geocoding?a=成都市”); // jquery返回的promise对象 let nativePromise = Promise.resolve(jqueryPromise); nativePromise.then(value => { console.info(value); // API接口的返回值 });

当接收到thenable类型对象(由ES6 Promise提出的Thenable是指具有.then()方法的对象)的时候,返回一个具有该then方法的崭新Promise对象;

let thenablePromise = Promise.resolve( // thenable类型对象 { then: (onFulfill, onReject) => { onFulfill(fulfilled!); } } ); console.log(thenablePromise instanceof Promise) // 打印true thenablePromise.then(value => { console.info(value); }, function(error) { console.error(error); }); // true // fulfilled!

当接收其它数据类型参数的时候(字符串或null)将会返回一个将该参数做为值的崭新Promise对象。

let promise = Promise.resolve([1, 2, 3]); promise.then(values => { console.info(values[0]); // 1 });

Promise.reject(reason)

返回一个被reason拒绝的Promise,但是与resolve()不同之处在于,即使reject()接收的参数是一个Promise对象,该表达式仍然会返回一个崭新的Promise。

let rejectedPromise = Promise.reject(new Error(“this is a error!”)); console.info( rejectedPromise === Promise.reject(rejectedPromise) // 打印false );

Promise.all(iterable)

当iterable参数中所有Promise都被resolve, 或者参数并不包含Promise时, all()方法返一个回resolve的崭新Promise对象。当iterable中一个Promise返回拒绝reject时, all()方法会立即终止并返回一个reject的崭新Promise对象。

let promise1 = Promise.resolve(1), promise2 = Promise.resolve(2), promise3 = Promise.resolve(3); Promise.all([promise1, promise2, promise3]).then(results => { console.info(results); // 打印[1, 2, 3] });

Promise.race(iterable)

当iterable参数数组中的任意一个Promise对象变为resolve或者reject状况,该表达式就会立刻返回一个崭新的Promise对象,并采用该Promise对象展开resolve或者reject。

let promise1 = Promise.resolve(1), promise2 = Promise.resolve(2), promise3 = Promise.resolve(3); Promise.race([promise1, promise2, promise3]).then(results => { console.info(results); // 打印 1 });

Promise方法链

异步事件管理之Promise详解
function taskA() { console.log(“Task A”); } function taskB() { console.log(“Task B”); } function onRejected(error) { console.log(“Catch Error: A or B”, error); } function finalTask() { console.log(“Final Task”); } let promise = Promise.resolve(); promise .then(taskA) .then(taskB) .catch(onRejected) .then(finalTask);

如果taskA()发生异常的话,会依照taskA() → onRejected() → finalTask()这个流程展开处理,taskB()并不会被调用的。

在上面代码taskA()或taskB()的处理中,如果发生异常或者返回了Rejected状况的Promise对象,就会调用onRejected()方法。

Promise中的各个Task相互独立,如果taskA()想为taskB()传递参数,须要在taskA()当中return相应的值,该值将会做为taskB()的参数。

function increment(value) { return value + 1; } function doubleUp(value) { return value * 2; } function output(value) { console.log(value); // => (1 + 1) * 2 } let promise = Promise.resolve(1); promise .then(increment) .then(doubleUp) .then(output) .catch(function(error) { console.error(error); // Promise Chain发生异常时调用 });
return的值不仅只局限于字符串或者数值,也可以是普通对象或者Promise。return值经由Promise.resolve( return返回值 );包装处理,无论反弹表达式内部返回什么,then()方法总是返回一个新建的Promise对象。

then()方法内的表达式是触发器调用的

执行Promise.resolve(value)等方法时,如果Promise对象立刻进入resolve状况,则是否意味.then()里指定的表达式是同步调用的?

var promise = new Promise((resolve) => { console.log(“Inner Promise”); // 1 resolve(2018); }); promise.then((value) => { console.log(value); // 3 }); console.log(“Outer Promise”); // 2 // Inner Promise // Outer Promise // 2018

上面的示例代码说明:传入then()内的表达式是被触发器执行的。JavaScript代码编写原则之一是尽量不要对异步反弹表达式展开同步调用,否则处理顺序可能会与预期不符,甚至导致栈溢出或异常处理错乱;如果须要在将来某个时刻调用触发器反弹表达式,可以采用setTimeout()、setInterval()等触发器API(绑定的表达式不会立刻执行,而是延迟到队列的最终)。

为了避免与此同时采用同步、触发器调用可能引起的混乱,Promise规范化约定只能采用触发器调用形式 。

每次调用then()都会返回新建的Promise

Promise之所以能够展开链式的方法调用,是由于无论then()还是catch()都会返回一个崭新的Promise对象。

let promise = new Promise((resolve) => { resolve(2018); }); let thenPromise = promise.then((value) => { console.log(value); }); let catchPromise = thenPromise.catch((error) => { console.error(error); }); console.info(promise === thenPromise); // false console.info(thenPromise === catchPromise); // false

通过===展开严格相等较为,可以看出上述3个Promise对象是互不相同的,也就证明then()和catch()都返回了不同的Promise对象。

异步事件管理之Promise详解

下面是一个通过then()返回新创建Promise对象的错误采用方法:

/* 错误的返回形式 */ function incorrectAsyncCall() { let promise = Promise.resolve(); promise.then(() => { return value; }); return promise; }

上述写法存在诸多问题,首先在promise.then()中产生的异常不会被外部捕获,其次也不能得到then()的返回值。由于每次调用promise.then()都会返回一个新创建的Promise对象,因此须要通过Promise Chain将调用链式化,修改后的代码如下:

/* 正确的做法 */ function correctAsyncCall() { let promise = Promise.resolve(); // 间接将链式调用的then()返回 return promise.then(() => { return value; }); }
与Promise.then()类似,包括Promise.all()和Promise.race()都会接收Promise对象做为参数,然后返回一个与接收参数不同的崭新Promise对象,采用时应多加注意。

采用reject而不是throw

Promise的构造表达式以及then()中执行的表达式都可以认为是在try…catch块中运行,因此即便采用了throw,程序本身也不会因为抛出异常而终止。

let promise = new Promise((resolve, reject) => { throw new Error(“throw message”); }); promise.catch(error => { console.error(error); // => “throw message” });

上面的代码运行时没有任何问题,但是如果须要把Promise对象状况设置为Rejected的话,相比throw关键字reject()方法会更加合理。接下来,我们方便的通过Promise构造表达式中的reject参数,将Promise对象的状况设置为Rejected。

let promise = new Promise((resolve, reject) => { reject(new Error(“reject message”)); // 发生错误的时候可以向reject()传递Error对象 }); promise.catch(error => { console.error(error); // => “reject message” });
reject比throw更安全另一个原因在于,有些场景下很难届定throw是开发人员主动抛出,还因为真正的代码异常导致的。

当须要在then()中执行reject操作的时候,可以通过then()当中的反弹表达式return一个自定义的Promise对象。然后根据这个自定义Promise对象的状况,下一个then()中注册的的onFulfilled()或onRejected()反弹表达式会相应展开调用,从而与此同时实现then()中不通过throw关键字也能展开reject操作。

let promise = Promise.resolve(); promise .then(() => { let newPromise = new Promise((resolve, reject) => { // resolve或者reject的状况决定了下一个then()当中的onFulfilled或onRejected哪个会被调用 }); return newPromise; }) .then(onFulfilled, onRejected);

例如下面代码中,newPromise对象状况为Rejected的时候,后续catch()中的onRejected方法会被调用。

let onRejected = console.error.bind(console); let promise = Promise.resolve(); promise .then(() => { let newPromise = new Promise((resolve, reject) => { reject(new Error(“Promise is rejected !”)); }); return newPromise; }) .catch(onRejected);

接下来,再通过Promise.reject()来简化代码:

let onRejected = console.error.bind(console); let promise = Promise.resolve(); promise .then(() => { return Promise.reject(new Error(“Promise is rejected !”)); }) .catch(onRejected);

Promise的缺点在于一旦新建就会立即执行,并且无法中途取消。其次,如果不设置反弹表达式,Promise内部抛出的错误,不会反应到外部。第三,当处在pending状况时,无法得知目前进展到哪一个期(刚刚开始还是即将完成)。

因此,Promise规范化之后,逐渐派生出了更加便捷的async/await,以及更加强大的RxJS,为JavaScript前端触发器程式设计提供了有力支持,中后期本栏将依次展开撰述。

热烈欢迎优秀原创控制技术类、IT产业经济类该文向chan【成都IT圈】专栏展开投稿。

热烈欢迎我们高度关注社会公众号【IT圈】:Electronics, Embedded & Web

异步事件管理之Promise详解

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务