如何理解 JavaScript 的 Promises 模式?

2022-12-30 0 817

JavaScript 的 Promises 商业模式,许多人都不认知,那时话虽如此题主的那个试题,我在此撷取一则胡须哥的控制技术贴给我们,期望对我们认知那个习题略有协助。

原作:Barret Lee 原镜像:http://www.
cnblogs.com/hustskyking/p/promise.html

原原文:对个人网志Ajax 出现的时候,吹来了一阵阵触发器之风,现在 Nodejs 火热,又一阵阵触发器狂风暴雨刮了回来。市场需求是愈来愈严苛,使用者对操控性的要求也是愈来愈高,接踵而至的是网页触发器操作方式成分股般增长,假如不能正确的contacts方法论,他们就会陷于无穷无尽反弹冥界中。

ECMAScript 6 已经将触发器操作方式列入了规范化,当代应用程序也内建了 Promise 第一类供他们进行触发器程式设计,那么此时此刻,急忙自学自学 Promise 的外部基本原理吧!

第二章 介绍 Promise

一、情景重现

由于 javascript 的Renderscript物理性质,他们要等候上一个该事件继续执行顺利完成就可以处置下一步棋,如下表所示:

// DOM ready后继续执行 $(document).ready(function(){ 2, function(data){ // 构筑 DOMString makeHtml(tpl, data, function(str){ // 填入到 DOM 中 $(obj).html(str); }); }); }); });

为的是增加井字统计数据的读取,他们将一些模版和大部份统计数据都放到服务端,当使用者操作方式某一按键时,需要将模版和统计数据堆叠起来插入到 DOM

中,那个过程还要在 DOMReady

后就可以继续执行。这种情况是极为常用的,假如触发器操作方式

再多些,整座标识符的对齐让人看著很不难受,为的是典雅地处置那个问题,ECMAScript 6

导入了 Promise 的概念,目前一些当代应用程序已经支持这些新东西了!

二、模型

为的是让标识符流程更加清晰,他们假想着能够按照下面的流程来跑程序:

new Promise(ready).then(getTpl).then(getData).then(makeHtml).resolve();
先将要事务按照继续执行顺序依次 push 到事务队列中,push 完了后再通过 resolve 函数启动整座流程。

整座流程的操作方式模型如下表所示:

promise(ok).then(ok_1).then(ok_2).then(ok_3).reslove(value)——+ | | | | | | | | | +=======+ | | | | | | | | | | | | | | | +———|———-|———-|——–→ ok() ←——+ | | | | ↓ | | | | | ↓ | +———-|———-|——–→ ok_1()| | | | ↓ | | | | ↓ | +———-|——–→ ok_2()| | | ↓ | | | ↓ | +——–→ ok_3()—–+ | | | | | ↓ @ Created By Barret Lee +=======+ exit
在 resolve 之前,promise 的每一个 then 都会将反弹函压入队列,resolve 后,将 resolve 的值送给队列的第一个函数,第一个函数继续执行完毕后,将继续执行结果再送入下一个函数,依次继续执行完队列。一连串下来,一气呵成,没有丝毫间断。

三、简单的封装

假如了解 Promise,可以移步下方,看看对 Promise 的封装:

Github: https://github.com/barretlee/myPromiseDEMO: http://barretlee.github.io/myPromise/index.html假如还不是很介绍,可以往下阅读全文,介绍一二。

第二章 Promise 基本原理

一、什么是 Promise ?

那么,什么是 Promise ?

Pr后再将拼合的统计数据填入到 DOM 中,这里他们将整座程序分解成多个事务:
↓ 事务三: 拼合后填入到 DOM
在事务一结束之前,也就是模版标识符

从服务器拉取回来之前,事务二和事务三都处 pending 状态,他们要等候上一个事务结束。而事务一结束后会将自身状态标记为 resolved,并把该事务中处置的结果移交给事务二继续处置(当然,这里假如没有统计数据返回,事务二就不会获得上一个事务的统计数据),依次类推,直到最后一个事务操作方式结束。

么后续的大部份事务都会将自己标记为 rejected,其标记理由(reason)就是出错事务的报错信息(那个报错信息可以使用 try…catch 来捕获,也可以通过程序自身来捕获,如 ajax 的 onerror 该事件、ajax 返回的状态码为 404 等)。

小结:Promise 就是一个事务的管理器。他的作用就是将各种内嵌反弹的事务用流水形式表达,其目的是为的是简化程式设计,让标识符方法论更加清晰。

由于整座程序的实现比较难认知,对于 Promise,他们将分为两部分阐述:

无错误传递的 Promise,也就是事务不会因为任何原因中断,事务队列中的事项都会被依次处置,此过程中 Promise 只有 pend响下一个事务,则下一个事务也会 rejected,假如不会,下一个事务可以正常继续执行,依次类推。

二、无错误传递的 Promise(简化版的 Promise)

首先,他们需要用一个变量(status)来标记事务的状态,然后将事务(affair)也保存到 Promise 第一类中。

var Promise = function(affair){ this.state = “pending”; this.affair = affair || function(o) { return o; }; this.allAffairs = []; };

Promise 有两个重要的方法,一个是 then,另一个是 resolve:

then,将事务添加到事务队列(allAffairs)中resolve,开启流程,让整座操作方式从第一个事务开始继续执行

在操作方式事务之前,他们会先把各种事务依次放入事务队列中,这里会用到 then 方法:

Promise.prototype.then = function (nextAffair){ var promise = new Promise(); if (this.state == ‘resloved’){ // 假如当前状态是已顺利完成,则那个事务将会被立即继续执行 return this._fire(promise, nextAffair); }else{ // 否则将会被加入队列中 return this._push(promise, nextAffair); } };

假如整座操作方式已经顺利完成了,那 then 方法送进的事务会被立即继续执行,

Promise.prototype._fire = function (nextPromise, nextAffair){ var nextResult = nextAffair(this.result); if (nextResult instanceof Promise){ nextResult.then(function(obj){ nextPromise.resolve(obj); }); }else{ nextPromise.resolve(nextResult); } return nextPromise; }; 被立即继续执行后会返回一个结果,那个结果会被传递到下一个事务中作为原料,但是这里需要考虑两种情况:
触发器,假如那个结果也是一个 Promise,则需要等待那个 Promise 继续执行完毕再将最终的结果传到下一个事务中。同步,假如那个结果不是 Promise,则直接将结果传递给下一个事务。第一种情况还是比较常用的,比如他们在一个事务中有一个子事务队列需要处置,此时要等候子事务顺利完成就可以回到主事务队列中。
Promise.prototype.resolve = function (obj){ if (this.state != ‘pending’) { throw ‘流程已顺利完成,不能再次开启流程!’; } this.state = ‘resloved’; // 继续执行该事务,并将继续执行结果寄存到 Promise 管理器上 this.result = this.affair(obj); for (var i = 0, len = this.allAffairs.length; i < len; ++i){ // 往后继续执行事务 var affair = this.allAffairs[i]; this._fire(affair.promise, affair.affair); } return this; };

resolve 接受一个参数,那个统计数据是交给第一个事务来处置的,因为第一个事务的启动可能需要点原料,那个统计数据就是原料,它也可以是空。该事物处置完毕后,将操作方式结果(result)寄存在 Promise 第一类上,方便引用,然后将结果(result)作为原料送入下一个事务。依次类推。

他们看到 then 方法中还调用了一个 _push ,那个方法的作用是将事务推进事务管理器(Promise)。
Promise.prototype._push = function (nextPromise, nextAffair){ this.allAffairs.push({ promise: nextPromise, affair: nextAffair }); return nextPromise; };

以上操作方式,他们就实现了一个简单的事务管理器,可以测试下下面的标识符:

// 初始化事务管理器 var promise = new Promise(function(data){ console.log(data); return 1; }); // 添加事务 promise.then(function(data){ console.log(data); return 2; }).then(function(data){ console.log(data); return 3; }).then(function(data){ console.log(data); console.log(“end”); }); // 启动事务 promise.resolve(“start”);

可以看到依次输出的结果为:

> start > 1 > 2 > 3 > end

由于上述实现极为简陋,链式调用没做太好的处置,请读者自行完善。

下面是一个触发器操作方式演示:

var promise = new Promise(function(data){ console.log(data); return “end”; }); promise.then(function(data){ // 这里需要返回一个 Promise,让主事务切换到子事务处置 return (function(data){ // 创建一个子事务 var promise = new Promise(); setTimeout(function(){ console.log(data); // 一秒后才启动子事务,模拟触发器延时 promise.resolve(); }, 1000); return promise; })(data); }); promise.resolve(“start”); 可以看到依次输出的结果为: > start > end (1s后输出)

将函数写的稍微好看点:

function delay(data){ // 创建一个子事务 var promise = new Promise(); setTimeout(function(){ console.log(data); // 一秒后才启动子事务,模拟触发器延时 promise.resolve(); }, 1000); return promise; } // 主事务 var promise = new Promise(function(data){ console.log(data); return “end”; }); promise.then(delay); promise.resolve(“start”);

三、包含错误传递的 Promise

真的很羡慕你能看到这么详细的文章,当然,后面会更加精彩!

没有错误处置的 Promise 只能算是一个半成品,虽说可以通过在最外层加一个 try..catch

来捕获错误,但没法具体定位是哪个事务发生的错误。并且这里的错误不仅仅包含 JavaScript Error,还有诸如 ajax 返回的 data code 不是 200 的情况等。

先看一个应用程序内建 Promise 的实例(该标识符可在当代应用程序下运行):

new Promise(function(resolve, reject){ resolve(“start”); }).then(function(data){ console.log(data); throw “error”; }).catch(function(err){ console.log(err); return “end”; }).then(function(data){ console.log(data) }); Promise 的反弹和 then 方法都是接受两个参数: new Promise(function(resolve, reject){ // … }); promise.then( function(value){/* code here */}, function(reason){/* code here */} );
事务处置过程中,假如有值返回,则作为 value,传入到 resolve 函数中,若有错误产生,则作为 reason 传入到reject

函数中处置。

在初始化 Promise 第一类时,若传入的反弹中没有继续执行 resolve 或者 reject,这需要他们主动去启动事务队列。
promise.resolve(); promise.reject();

上面两种都是可以启动一个队列的。这里跟第二章第二节的 resolve 函数用法类似。Promise 第一类还提供了 catch 函数,起用法等价于下面所示:

promise.catch(); // 等价于 promise.then(null, function(reason){});

还有两个 API:

promise.all(); promise.race();

后续再讲。先看看那个有错误处置的 Promise 是怎样实现的。

function Promise(resolver){ this.status = “pending”; this.value = null; this.handlers = []; this._doPromise.call(this, resolver); }

_doPromise 方法在实例化 Promise 函数时就继续执行。假如送入的反弹函数 resolver 中已经 resolve 或者 reject 了,程序就已经启动了,所以在实例化的时候就开始判断。

_doPromise: function(resolver){ var called = false, self = this; try{ resolver(function(value){ // 假如没有 call 则继续,并标记 called 为 true !called && (called = !0, self.resolve(value)); }, function(reason){ // 同上 !called && (called = !0, self.reject(reason)); }); } catch(e) { // 同上,捕获错误,传递错误到下一个 then 事务 !called && (called = !0, self.reject(e)); } },

只要 resolve 或者 reject 就会标记程序 called 为 true,表示程序已经启动了。

resolve: function(value) { try{ if(this === value){ throw new TypeError(‘流程已顺利完成,不能再次开启流程!’); } else { // 假如还有子事务队列,继续继续执行 value && value.then && this._doPromise(value.then); } // 继续执行完了后标记为顺利完成 this.status = “fulfilled”; this.value = value; this._dequeue(); } catch(e) { this.reject(e); } }, reject: function(reason) { // 标记状态为出错 this.status = “rejected”; this.value = reason; this._dequeue(); },

可以看到,每次 resolve 的时候都会用一个 try..catch 包裹来捕获未知错误。

_dequeue: function(){ var handler; // 继续执行事务,直到队列为空 while (this.handlers.length) { handler = this.handlers.shift(); this._handle(handler.thenPromise, handler.onFulfilled, handler.onRejected); } },

无论是 resolve 还是 reject 都会让程序往后奔流,直到结束大部份事务,所以这两个方法中都有 _dequeue 函数。

_handle: function(thenPromise, onFulfilled, onRejected){ var self = this; setTimeout(function() { // 判断下次操作方式采用哪个函数,reject 还是 resolve var callback = self.status == “fulfilled” ? onFulfilled : onRejected; // 只有是函数才会继续反弹 if (typeof callback === ‘function’) { try { self.resolve.call(thenPromise, callback(self.value)); } catch(e) { self.reject.call(thenPromise, e); } return; } // 否则就将 value 传递给下一个事务了 self.status == “fulfilled” ? self.resolve.call(thenPromise, self.value) : self.reject.call(thenPromise, self.value); }, 1); },

那个函数跟上一节提到的 _fire 类似,假如 callback 是 function,就会进入子事务队列,处置完了后退回到主事务队列。最后一个 then 方法,将事务推进队列。

then: function(onFulfilled, onRejected){ var thenPromise = new Promise(function() {}); if (this.status == “pending”) { this.handlers.push({ thenPromise: thenPromise, onFulfilled: onFulfilled, onRejected: onRejected }); } else { this._handle(thenPromise, onFulfilled, onRejected); } return thenPromise; }

假如第二节没有认知清楚,这一节也会让人头疼,这一部分讲的比较粗糙。

第三章 触发器程式设计

一、jQuery 中的 Defferred 第一类

或许你在面试的时候,有面试官问你:

$.ajax() 继续执行后返回的结果是什么?

在 jQuery1.5 版本就已经导入了 Defferred 第一类,当时为的是导入那个东西,整座 jQuery 都被重构了。Defferred 跟 Promise 类似,它表示一个还未顺利完成任务的第一类,而 Promise 确切的说,是一个代表未知值的第一类。
$.ajax({ url: url }).done(function(data, status, xhr){ //… }).fail(function(){ //… });

回忆下第二章第一节中的 Promise,是不是如出一辙,只是 jQuery 还提供了更多的语法糖:

$.ajax({ url: url, success: function(data){ //… }, error: funtion(){ //… } });

他允许将 done 和 fail 两个函数的反弹放到 ajax 初始化的参数 success 和 fail 上,其基本原理还是一样的,同样,还有这样的东西:

$.when(taskOne, taskTwo).done(function () { console.log(“都继续执行完毕后才会输出我!”); }).fail(function(){ console.log(“只要有一个失败,就会输出我!”) }); 当 taskOne 和 taskTwo 都顺利完成后才执行 done 反弹,那个应用程序内建的 Promise 也有对应的函数: Promise.all([true, Promise.resolve(1), …]).then(function(value){ //…. });

应用程序内建的 Promise 还提供了一个 API:

Promise.race([true, Promise.resolve(1), …]).then(function(value){ //…. }, function(reason){ //… });

只要 race 参数中有一个 resolve 或者 reject,then 反弹就会出发。

二、基于事件响应的触发器模型

@朴灵 写的 EventProxy 就是基于该事件响应的触发器模型,按理说,那个实现的方法论是最清晰的,不过标识符量稍微多一点。

function taskA(){ setTimeout(function(){ var result = “A”; E.emit(“taskA”, result); }, 1000); } function taskB(){ setTimeout(function(){ var result = “B”; E.emit(“taskB”, result); }, 1000); } E.all([“taskA”, “taskB”], function(A, B){ return A + B; });

我没有看他的源码,但是想想,应该是那个方法论。只需要在消息中心管理各个 emit 以及消息注册。这里的错误处置值得思考下。

在半年前,也写过一则关于触发器程式设计的文章:JavaScript触发器程式设计基本原理,感兴趣的可以去读一读。

第四章 小结

文章比较长,主要解说了 Promise 的应用情景和实现基本原理,假如你能够顺畅的读完全文并且指出文中的一些错误,说明你已经悟到了。

相关文章

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

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