剖析Promise内部结构,一步一步实现一个完好的、能通过具有Test case的Promise类

【转】你必须明白的EF知识以及经验

正文写给来必然Promise使用更的人,如果您还尚未下过Promise,这篇稿子或无吻合你,建议优先了解Promise的使用

小心:以下内容如果没有特别说明,默认使用的EF6.0版本,code first模式。

Promise标准解读

1.才生一个then方法,没有catch,race,all等艺术,甚至未曾构造函数

Promise标准被只是指定了Promise对象的then方法的作为,其它任何我们周边的法子/函数都并无点名,包括catch,race,all等常用方法,甚至也无点名该怎么组织出一个Promise对象,另外then也从未一般实现中(Q,
$q等)所支撑的老三单参数,一般称onProgress

2.then办法返回一个新的Promise

Promise的then方法返回一个初的Promise,而不是回去this,此处在下文会有重复多说

promise2 = promise1.then(alert)
promise2 != promise1 // true

3.不同Promise的兑现用可以彼此调用(interoperable)

4.Promise的初步状态为pending,它可透过状态转换为fulfilled(本文为一致把这态称为resolved)或者rejected,一旦状态确定,就无得以又转移为任何状态,状态确定的过程叫settle

5.双重有血有肉的业内见此

推荐MiniProfiler插件

工欲善其事,必先利其器。

咱俩下EF和当那个充分程度增长了开发进度,不过就带动的是众性能低下的写法和转不极端高速之sql。

则咱得使SQL Server
Profiler来监控执行的sql,不过个人觉得就是麻烦,每次需要开辟、过滤、清除、关闭。

以此间强烈推荐一个插件MiniProfiler。实时监控页面请求对承诺实行的sql语句、执行时。简单、方便、针对性强。

如图:(切切实实以与介绍请动)

同等步一步实现一个Promise

下我们就算来平等步一步实现一个Promise

数量准备

新建实体:Score(成绩分数表)、Student(学生说明)、Teacher(老师表)

末尾会被出demo代码下充斥链接

构造函数

盖专业并不曾点名如何组织一个Promise对象,所以我们一样为时貌似Promise实现中通用的不二法门来布局一个Promise对象,也是ES6本来生Promise里所用的办法,即:

// Promise构造函数接收一个executor函数,executor函数执行完同步或异步操作后,调用它的两个参数resolve和reject
var promise = new Promise(function(resolve, reject) {
  /*
    如果操作成功,调用resolve并传入value
    如果操作失败,调用reject并传入reason
  */
})

俺们先实现构造函数的框架如下:

function Promise(executor) {
  var self = this
  self.status = 'pending' // Promise当前的状态
  self.data = undefined  // Promise的值
  self.onResolvedCallback = [] // Promise resolve时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面
  self.onRejectedCallback = [] // Promise reject时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面

  executor(resolve, reject) // 执行executor并传入相应的参数
}

地方的代码基本落实了Promise构造函数的主体,但时还有零星个问题:

1.咱们给executor函数传了有限独参数:resolve和reject,这简单个参数目前还不曾概念

2.executor生或会见出错(throw),类似下面这样,而如果executor出错,Promise应该于那throw出之值reject:

new Promise(function(resolve, reject) {
  throw 2
})

据此我们得以构造函数里定义resolve和reject这有限单函数:

function Promise(executor) {
  var self = this
  self.status = 'pending' // Promise当前的状态
  self.data = undefined  // Promise的值
  self.onResolvedCallback = [] // Promise resolve时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面
  self.onRejectedCallback = [] // Promise reject时的回调函数集,因为在Promise结束之前有可能有多个回调添加到它上面

  function resolve(value) {
    // TODO
  }

  function reject(reason) {
    // TODO
  }

  try { // 考虑到执行executor的过程中有可能出错,所以我们用try/catch块给包起来,并且在出错后以catch到的值reject掉这个Promise
    executor(resolve, reject) // 执行executor
  } catch(e) {
    reject(e)
  }
}

有人可能会见咨询,resolve和reject这半单函数能不能不定义在构造函数里吧?考虑到我们以executor函数里是坐resolve(value),reject(reason)的样式调用的当即半独函数,而非是以resolve.call(promise,
value),reject.call(promise,
reason)这种形式调用的,所以就半独函数在调用时之内也肯定发生一个带有的this,也就是说,要么马上点儿独函数是通过bind后传被了executor,要么它们定义在构造函数的里,使用self来访问所属之Promise对象。所以要是我们怀念把立即片只函数定义在构造函数的外部,确实是好如此形容的:

function resolve() {
  // TODO
}
function reject() {
  // TODO
}
function Promise(executor) {
  try {
    executor(resolve.bind(this), reject.bind(this))
  } catch(e) {
    reject.bind(this)(e)
  }
}

只是明显,bind也会见回一个初的函数,这么一来还是相当给每个Promise对象都产生有属于自己的resolve和reject函数,就同写于构造函数内部没什么区别了,所以我们尽管直接把当下片单函数定义在构造函数里面了。不过话说回来,如果浏览器对bind的所优化,使用后同栽形式应好升官一下内存以频率。

此外我们这里的贯彻并无考虑隐藏this上之变量,这让这个Promise的状态好于executor函数外部为转移,在一个指谱的实现里,构造出底Promise对象的状态和结尾结果当是无法从外表更改的。

连着下,我们贯彻resolve和reject这简单个函数

function Promise(executor) {
  // ...

  function resolve(value) {
    if (self.status === 'pending') {
      self.status = 'resolved'
      self.data = value
      for(var i = 0; i < self.onResolvedCallback.length; i++) {
        self.onResolvedCallback[i](value)
      }
    }
  }

  function reject(reason) {
    if (self.status === 'pending') {
      self.status = 'rejected'
      self.data = reason
      for(var i = 0; i < self.onRejectedCallback.length; i++) {
        self.onRejectedCallback[i](reason)
      }
    }
  }

  // ...
}

多就是是当认清状态也pending之后把状态改吗对应的值,并把相应的value和reason存在self的data属性上面,之后执行相应的回调函数,逻辑很简单,这里就无多讲了。

foreach循环的陷进 

1.有关延迟加载

央看上图红框。为什么StudentId有价,而Studet为null?因为运用code
first,需要装导航属性为virtual,才会加载延迟加载数据。

2.有关以循环中走访导航属性的良处理(接着上面,加上virtual后会见报以下很)

“已出开拓的跟此 Command 相关联的
DataReader,必须首先将她倒闭。”

化解方案:

  • 方案1、设定ConnectionString加上MultipleActiveResultSets=true,但特适用于SQL
    2005过后的本
  • 方案2、或者先念来放置于List中

3.上述两接触止为热身,我们说的陷阱才刚刚开始!

然后我们点击打开MiniProfiler工具(不要被吓到)

缓解方案:使用Include显连续查询(注意:需要手动导入using System.Data.Entity
不然Include只能传表名字符串)。

再看MiniProfiler的督察(瞬间101长条sql变成了1长,这之中的性能可想而知。)

then方法

Promise对象来一个then方法,用来注册在斯Promise状态确定后底回调,很显然,then方法要写于原型链上。then方法会返回一个Promise,关于这或多或少,Promise/A+标准并从未要求回的之Promise是一个新的对象,但当Promise/A标准中,明确规定了then要回去一个初的对象,目前之Promise实现中then几乎都是回到一个初的Promise(详情)对象,所以当我们的落实着,也受then返回一个新的Promise对象。

关于这或多或少,我认为专业被是发出雷同碰矛盾的:

标准被说,如果promise2 =
promise1.then(onResolved,
onRejected)里的onResolved/onRejected返回一个Promise,则promise2直接获取此Promise的状态与价值也我因此,但考虑如下代码:

promise2 = promise1.then(function foo(value) {
  return Promise.reject(3)
})

AutoMapper工具

点我们透过Include显示的执行表的连查询显然是天经地义的,但尚不够。如果我们仅需要查询数据的某些字段呢,上面查询有字段岂不是杀浪费内存存储空间及应用程序与数据库数据传带富。

咱俩可:

针对许监督及之sql:

咱们看变化的sql,查询的字段少了许多。只有我们来得列下字段的跟一个StudentId,StudentId用来连接查询条件的。

然,这样的不二法门很不利。可是有无发什么又好之方案或措施呢?答案是必定的。(不然,也未见面在此处屁话了。)如果表字段非常多,我们用采用的字段也不行多,导航属性为够呛多之时段,这样的手动映射就显示不那么尴尬了。那么连下去我们开始介绍下AutoMapper来好投:

小心:首先需要NuGet下载AutoMapper。(然后导入命名空间 using
AutoMapper; using AutoMapper.QueryableExtensions;)

咱视地方查询语句没有一个个之手动映射,而映射都是独布置了。其中CreateMap应该是要描绘到Global.asax文件之中的。(其实呢就是分别了炫耀部分,清晰了询问语句。细心的同窗也许注意到了,这种办法还无去了当仁不让Include)

我们看看了变更的sql和前有微微见仁见智,但单纯大成了同样长条sql,并且结果也是无可非议的。(其实就是大半矣同等修CASE WHEN ([Extent2].[Id] IS
NOT NULL) THEN 1 END AS
[C1]。看起就长长的告句子并没啊实际意义,然而就是AutoMapper生成的sql,同时自耶代表不知道为什么跟EF生成的例外)

这样做的裨益?

  1. 避在循环中做客导航属性多次实施sql语句。
  2. 避了查询语句被极度多之手动映射,影响代码的开卷。

至于AutoMapper的任何组成部分资料:

http://www.cnblogs.com/xishuai/p/3712361.html

http://www.cnblogs.com/xishuai/p/3700052.html

http://www.cnblogs.com/farb/p/AutoMapperContent.html

这边设foo运行了,则promise1的状态必然就规定还为resolved,如果then返回了this(即promise2

promise1),说明promise2和promise1是和一个目标,而这时候promise1/2的状态都规定,没有办法再获Promise.reject(3)的状态及结果为己因此,因为Promise的状态确定后即使不得再换为外状态。

此外每个Promise对象都好当其上翻来覆去调用then方法,而每次调用then返回的Promise的状态在那同样不好调动用then时传出参数的回值,所以then不克回到this,因为then每次回来的Promise的结果还出或两样。

脚我们来落实then方法:

// then方法接收两个参数,onResolved,onRejected,分别为Promise成功或失败后的回调
Promise.prototype.then = function(onResolved, onRejected) {
  var self = this
  var promise2

  // 根据标准,如果then的参数不是function,则我们需要忽略它,此处以如下方式处理
  onResolved = typeof onResolved === 'function' ? onResolved : function(v) {}
  onRejected = typeof onRejected === 'function' ? onRejected : function(r) {}

  if (self.status === 'resolved') {
    return promise2 = new Promise(function(resolve, reject) {

    })
  }

  if (self.status === 'rejected') {
    return promise2 = new Promise(function(resolve, reject) {

    })
  }

  if (self.status === 'pending') {
    return promise2 = new Promise(function(resolve, reject) {

    })
  }
}

Promise总共有三栽或的状态,我们分三只if片来拍卖,在内部分别都回到一个new
Promise。

据悉专业,我们懂得,对于如下代码,promise2的价在then里面函数的返回值:

promise2 = promise1.then(function(value) {
  return 4
}, function(reason) {
  throw new Error('sth went wrong')
})

而promise1被resolve了,promise2的将为4
resolve,如果promise1被reject了,promise2将吃new Error(‘sth went wrong’)
reject,更多复杂的景况不再详述。

所以,我们得以then里面实践onResolved或者onRejected,并基于返回值(标准中记为x)来规定promise2的结果,并且,如果onResolved/onRejected返回的凡一个Promise,promise2将一直获得此Promise的结果:

Promise.prototype.then = function(onResolved, onRejected) {
  var self = this
  var promise2

  // 根据标准,如果then的参数不是function,则我们需要忽略它,此处以如下方式处理
  onResolved = typeof onResolved === 'function' ? onResolved : function(value) {}
  onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {}

  if (self.status === 'resolved') {
    // 如果promise1(此处即为this/self)的状态已经确定并且是resolved,我们调用onResolved
    // 因为考虑到有可能throw,所以我们将其包在try/catch块里
    return promise2 = new Promise(function(resolve, reject) {
      try {
        var x = onResolved(self.data)
        if (x instanceof Promise) { // 如果onResolved的返回值是一个Promise对象,直接取它的结果做为promise2的结果
          x.then(resolve, reject)
        }
        resolve(x) // 否则,以它的返回值做为promise2的结果
      } catch (e) {
        reject(e) // 如果出错,以捕获到的错误做为promise2的结果
      }
    })
  }

  // 此处与前一个if块的逻辑几乎相同,区别在于所调用的是onRejected函数,就不再做过多解释
  if (self.status === 'rejected') {
    return promise2 = new Promise(function(resolve, reject) {
      try {
        var x = onRejected(self.data)
        if (x instanceof Promise) {
          x.then(resolve, reject)
        }
      } catch (e) {
        reject(e)
      }
    })
  }

  if (self.status === 'pending') {
  // 如果当前的Promise还处于pending状态,我们并不能确定调用onResolved还是onRejected,
  // 只能等到Promise的状态确定后,才能确实如何处理。
  // 所以我们需要把我们的**两种情况**的处理逻辑做为callback放入promise1(此处即this/self)的回调数组里
  // 逻辑本身跟第一个if块内的几乎一致,此处不做过多解释
    return promise2 = new Promise(function(resolve, reject) {
      self.onResolvedCallback.push(function(value) {
        try {
          var x = onResolved(self.data)
          if (x instanceof Promise) {
            x.then(resolve, reject)
          }
        } catch (e) {
          reject(e)
        }
      })

      self.onRejectedCallback.push(function(reason) {
        try {
          var x = onRejected(self.data)
          if (x instanceof Promise) {
            x.then(resolve, reject)
          }
        } catch (e) {
          reject(e)
        }
      })
    })
  }
}

// 为了下文方便,我们顺便实现一个catch方法
Promise.prototype.catch = function(onRejected) {
  return this.then(null, onRejected)
}

时至今日,我们基本落实了Promise标准被所提到到的始末,但还有几单问题:

1.不一的Promise实现中要无缝的可是彼此,即Q的Promise,ES6的Promise,和咱们贯彻的Promise之间以及任何的Promise实现,应该同时是生必不可少无缝相互调用的,比如:

// 此处用MyPromise来代表我们实现的Promise
new MyPromise(function(resolve, reject) { // 我们实现的Promise
  setTimeout(function() {
    resolve(42)
  }, 2000)
}).then(function() {
  return new Promise.reject(2) // ES6的Promise
}).then(function() {
  return Q.all([ // Q的Promise
    new MyPromise(resolve=>resolve(8)), // 我们实现的Promise
    new Promise.resolve(9), // ES6的Promise
    Q.resolve(9) // Q的Promise
  ])
})

咱眼前实现的代码并无处理这样的逻辑,我们仅仅看清了onResolved/onRejected的返回值是否为我们贯彻之Promise的实例,并无举行任何其他的判断,所以地方这样的代码目前是从未艺术于咱们的Promise里正确运行的。

2.底这样的代码目前啊是绝非道处理的:

new Promise(resolve=>resolve(8))
  .then()
  .then()
  .then(function foo(value) {
    alert(value)
  })

是的的作为应是alert出8,而如果用我们的Promise,运行上述代码,将会alert出undefined。这种表现称为穿透,即8是值会穿透两只then(说Promise更为准确)到达最终一个then里之foo函数里,成为她的实参,最终将会alert出8。

下我们先是处理大概的事态,值的穿透

Promise值的穿透

通过观察,会发现我们意在下这段代码

new Promise(resolve=>resolve(8))
  .then()
  .catch()
  .then(function(value) {
    alert(value)
  })

及下面这段代码的一言一行是一致的

new Promise(resolve=>resolve(8))
  .then(function(value){
    return value
  })
  .catch(function(reason){
    throw reason
  })
  .then(function(value) {
    alert(value)
  })

故只要想使管then的可靠参留空且让值可以穿过外露到末端,意味着then的蝇头只参数的默认值分别吗function(value)
{return value},function(reason) {throw reason}。
所以我们就待将then里判断onResolved和onRejected的一对转化如下即可:

onResolved = typeof onResolved === 'function' ? onResolved : function(value) {return value}
onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {throw reason}

于是乎Promise神奇的值的穿透也未尝那黑魔法,只不过是then默认参数就是将价值为后传或抛

不同Promise的交互

至于不同Promise间的相,其实标准里是起说明的,其中详细指定了哪些通过then的实参返回的值来支配promise2的状态,我们只有待按照正规将正规化的内容改动成代码即可。

此处大概解释一下标准:

就是我们只要将onResolved/onRejected的归来值,x,当成一个恐怕是Promise的靶子,也便规范里所说的thenable,并坐无限保险的办法调用x上的then方法,如果大家都遵循规范兑现,那么差的Promise之间便足以并行了。而专业以保证起见,即使x返回了一个蕴含then属性但连无随Promise标准的目标(比如说这个x把它then里之有数只参数还调用了,同步还是异步调用(PS,原则达成then的蝇头单参数需要异步调用,下文会讲到),或者是失误后以调用了它,或者then根本未是一个函数),也会尽量正确处理。

有关为什么用不同之Promise实现能够互相交互,我怀念由该是肯定的,Promise并无是JS一早即使部分标准,不同第三着的实现中是并无互相了解的,如果您利用的某一个仓库中查封装了一个Promise实现,想象一下假设其不可知跟你协调使用的Promise实现相互之间的现象。。。

提议各位对照着标准看以下代码,因为专业对这个证实的好详尽,所以您该能够以随意一个Promise实现着找到类似的代码:

/*
resolvePromise函数即为根据x的值来决定promise2的状态的函数
也即标准中的[Promise Resolution Procedure](https://promisesaplus.com/#point-47)
x为`promise2 = promise1.then(onResolved, onRejected)`里`onResolved/onRejected`的返回值
`resolve`和`reject`实际上是`promise2`的`executor`的两个实参,因为很难挂在其它的地方,所以一并传进来。
相信各位一定可以对照标准把标准转换成代码,这里就只标出代码在标准中对应的位置,只在必要的地方做一些解释
*/
function resolvePromise(promise2, x, resolve, reject) {
  var then
  var thenCalledOrThrow = false

  if (promise2 === x) { // 对应标准2.3.1节
    return reject(new TypeError('Chaining cycle detected for promise!'))
  }

  if (x instanceof Promise) { // 对应标准2.3.2节
    // 如果x的状态还没有确定,那么它是有可能被一个thenable决定最终状态和值的
    // 所以这里需要做一下处理,而不能一概的以为它会被一个“正常”的值resolve
    if (x.status === 'pending') {
      x.then(function(value) {
        resolvePromise(promise2, value, resolve, reject)
      }, reject)
    } else { // 但如果这个Promise的状态已经确定了,那么它肯定有一个“正常”的值,而不是一个thenable,所以这里直接取它的状态
      x.then(resolve, reject)
    }
    return
  }

  if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) { // 2.3.3
    try {

      // 2.3.3.1 因为x.then有可能是一个getter,这种情况下多次读取就有可能产生副作用
      // 即要判断它的类型,又要调用它,这就是两次读取
      then = x.then 
      if (typeof then === 'function') { // 2.3.3.3
        then.call(x, function rs(y) { // 2.3.3.3.1
          if (thenCalledOrThrow) return // 2.3.3.3.3 即这三处谁选执行就以谁的结果为准
          thenCalledOrThrow = true
          return resolvePromise(promise2, y, resolve, reject) // 2.3.3.3.1
        }, function rj(r) { // 2.3.3.3.2
          if (thenCalledOrThrow) return // 2.3.3.3.3 即这三处谁选执行就以谁的结果为准
          thenCalledOrThrow = true
          return reject(r)
        })
      } else { // 2.3.3.4
        resolve(x)
      }
    } catch (e) { // 2.3.3.2
      if (thenCalledOrThrow) return // 2.3.3.3.3 即这三处谁选执行就以谁的结果为准
      thenCalledOrThrow = true
      return reject(e)
    }
  } else { // 2.3.4
    resolve(x)
  }
}

下一场我们采用这个函数的调用替换then里几乎远在判断x是否为Promise对象的职务即可,见下方完整代码。

说到底,我们刚说及,原则达成,promise.then(onResolved,
onRejected)里之即片互函数需要异步调用,关于这一点,标准里吗生说明:

In practice, this requirement ensures that onFulfilled and onRejected
execute asynchronously, after the event loop turn in which then is
called, and with a fresh stack.

据此我们得对咱的代码做一些转,即于四独地方长setTimeout(fn,
0),这点会在完全的代码中注释,请各位自行发现。

实在,即使你免参照标准,最终你在起测试时也会发觉而then的参数不因异步的方调用,有些情况下Promise会不按照预期的计行事,通过不停的自测,最终你早晚会吃then的参数异步执行,让executor函数立即施行。本人以平初始实现Promise时就从来不参考标准,而是自己管经验测试,最终发现的这个题材。

时至今日,我们不怕实现了一个的Promise,完整代码如下:

try {
  module.exports = Promise
} catch (e) {}

function Promise(executor) {
  var self = this

  self.status = 'pending'
  self.onResolvedCallback = []
  self.onRejectedCallback = []

  function resolve(value) {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }
    setTimeout(function() { // 异步执行所有的回调函数
      if (self.status === 'pending') {
        self.status = 'resolved'
        self.data = value
        for (var i = 0; i < self.onResolvedCallback.length; i++) {
          self.onResolvedCallback[i](value)
        }
      }
    })
  }

  function reject(reason) {
    setTimeout(function() { // 异步执行所有的回调函数
      if (self.status === 'pending') {
        self.status = 'rejected'
        self.data = reason
        for (var i = 0; i < self.onRejectedCallback.length; i++) {
          self.onRejectedCallback[i](reason)
        }
      }
    })
  }

  try {
    executor(resolve, reject)
  } catch (reason) {
    reject(reason)
  }
}

function resolvePromise(promise2, x, resolve, reject) {
  var then
  var thenCalledOrThrow = false

  if (promise2 === x) {
    return reject(new TypeError('Chaining cycle detected for promise!'))
  }

  if (x instanceof Promise) {
    if (x.status === 'pending') { //because x could resolved by a Promise Object
      x.then(function(v) {
        resolvePromise(promise2, v, resolve, reject)
      }, reject)
    } else { //but if it is resolved, it will never resolved by a Promise Object but a static value;
      x.then(resolve, reject)
    }
    return
  }

  if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) {
    try {
      then = x.then //because x.then could be a getter
      if (typeof then === 'function') {
        then.call(x, function rs(y) {
          if (thenCalledOrThrow) return
          thenCalledOrThrow = true
          return resolvePromise(promise2, y, resolve, reject)
        }, function rj(r) {
          if (thenCalledOrThrow) return
          thenCalledOrThrow = true
          return reject(r)
        })
      } else {
        resolve(x)
      }
    } catch (e) {
      if (thenCalledOrThrow) return
      thenCalledOrThrow = true
      return reject(e)
    }
  } else {
    resolve(x)
  }
}

Promise.prototype.then = function(onResolved, onRejected) {
  var self = this
  var promise2
  onResolved = typeof onResolved === 'function' ? onResolved : function(v) {
    return v
  }
  onRejected = typeof onRejected === 'function' ? onRejected : function(r) {
    throw r
  }

  if (self.status === 'resolved') {
    return promise2 = new Promise(function(resolve, reject) {
      setTimeout(function() { // 异步执行onResolved
        try {
          var x = onResolved(self.data)
          resolvePromise(promise2, x, resolve, reject)
        } catch (reason) {
          reject(reason)
        }
      })
    })
  }

  if (self.status === 'rejected') {
    return promise2 = new Promise(function(resolve, reject) {
      setTimeout(function() { // 异步执行onRejected
        try {
          var x = onRejected(self.data)
          resolvePromise(promise2, x, resolve, reject)
        } catch (reason) {
          reject(reason)
        }
      })
    })
  }

  if (self.status === 'pending') {
    // 这里之所以没有异步执行,是因为这些函数必然会被resolve或reject调用,而resolve或reject函数里的内容已是异步执行,构造函数里的定义
    return promise2 = new Promise(function(resolve, reject) {
      self.onResolvedCallback.push(function(value) {
        try {
          var x = onResolved(value)
          resolvePromise(promise2, x, resolve, reject)
        } catch (r) {
          reject(r)
        }
      })

      self.onRejectedCallback.push(function(reason) {
          try {
            var x = onRejected(reason)
            resolvePromise(promise2, x, resolve, reject)
          } catch (r) {
            reject(r)
          }
        })
    })
  }
}

Promise.prototype.catch = function(onRejected) {
  return this.then(null, onRejected)
}

Promise.deferred = Promise.defer = function() {
  var dfd = {}
  dfd.promise = new Promise(function(resolve, reject) {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}

联表查询统计

渴求:查询前100独学生考试项目(“模拟考试”、“正式考试”)、考试次数、语文平均分、学生姓名,且考试次数超过等于3赖。(按考试类别分类统计)

代码如下:

相这般的代码,我先是影响是惨痛了。又于循环执行sql了。监控如下:

事实上,我们特需要有些改变就将101久sql变成1长达,如下:

马上变1条。

俺们开拓查看详细的sql语句

意识这仅仅只是查询结果集合而已,其中的随考试项目来统计是先后用到有数据后每当计算的(而无是于数据库内计算,然后直接回结果),这样平等是荒废了数据库查询数据传。

关于连接查询分组统计我们可采取SelectMany,如下:

监察sql如下:(是未是精简多了邪?)

关于SelectMany资料:

http://www.cnblogs.com/lifepoem/archive/2011/11/18/2253579.html

http://www.cnblogs.com/heyuquan/p/Linq-to-Objects.html

测试

怎规定我们实现的Promise符合标准呢?Promise有一个配套的测试脚本,只待我们于一个CommonJS的模块中露一个deferred方法(即exports.deferred方法),就得了,代码见上述代码的末梢。然后实施如下代码即可行测试:

npm i -g promises-aplus-tests
promises-aplus-tests Promise.js

性提升的AsNonUnicode

监察及之sql

我们看EF正常情形变化的sql会当前边带齐“N”,如果我们添加DbFunctions.AsNonUnicode生成的sql是不曾“N”的,当您发现带来齐“N”的sql比无带“N”的
sql查询速度迟滞很多之时段那么便知晓该怎么惩罚。

(以前用oracle的早晚带非牵动“N”查询效率差别特别引人注目,今天因故sql
server测试并没有察觉什么区别。还有自己发觉EF6会根据数据库被凡nvarchar的时候才会生成带“N”的sql,oracle数据库没测试,有趣味之校友可以测试下)

至于Promise的另外问题

性能提升的AsNoTracking

咱们看变化的sql

sql是转变的平等模子一样,但是实施时间却是4.8倍。原因仅仅只是第一条EF语句子多加了一个AsNoTracking。

注意:

  • AsNoTracking干啊的啊?无跟踪查询而已,也就是说查询出来的靶子不能够直接开修改。所以,我们于开多少集合查询显示,而以未待针对聚集修改并创新至数据库的时光,一定毫无忘记加上AsNoTracking。
  • 假若查询过程做了select映射就无欲加AsNoTracking。如:db.Students.Where(t=>t.Name.Contains(“张三”)).select(t=>new
    (t.Name,t.Age)).ToList();

Promise的特性问题

可能各位看官会觉得奇怪,Promise能出什么性质问题呢?并从未大气底乘除啊,几乎都是处理逻辑的代码。

辩论及说,不克称为“性能问题”,而仅仅是生或出现的推移问题。什么意思吧,记得刚刚我们说用把4块代码包在setTimeout里吧,先考虑如下代码:

var start = +new Date()
function foo() {
  setTimeout(function() {
    console.log('setTimeout')
    if((+new Date) - start < 1000) {
      foo()
    }
  })
}
foo()

运作方面的代码,会打印出些许坏’setTimeout’呢,各位可友善试一下,不出意外的言辞,应该是250糟左右,我刚刚运行了平潮,是241潮。这证明,上述代码中简单不良setTimeout运行的工夫距离约是4ms(另外,setInterval也是相同的),实事上,这正是浏览器两涂鸦Event
Loop之间的流年间隔,相关专业各位好自动查阅。另外,在Node中,这个时刻距离和浏览器不雷同,经过自家之测试,是1ms。

唯有一个4ms底延迟或当相似的web应用中并无见面生什么问题,但是考虑极端气象,我们发出20独Promise链式调用,加上代码运行的日,那么这个链式调用的首先执行代码和最后一行代码的运作颇可能会见跨100ms,如果就之间无对UI有任何更新的语句,虽然本质上从来不啊性质问题,但也许会见招致一定之卡顿或者闪烁,虽然当web应用中这种状态并无广泛,但是在Node应用中,确实是出或出现如此的case的,所以一个克用叫生产环境之兑现有必不可少将这个延迟消除掉。在Node中,我们得以调用process.nextTick或者setImmediate(Q就是这么做的),在浏览器中现实怎么着做,已经高于了本文的议论范围,总的来说,就是咱需要贯彻一个函数,行为跟setTimeout一样,但其要异步且尽早的调用所有都进入队列的函数,这里发生一个贯彻。

多字段组合排序(字符串)

渴求:查询名字里含有“张三”的学生,先照名排序,再按年龄排序。

哎呀,不对啊。按名排序为年龄排序覆盖了。我们应为此ThenBy来做排序。

不错不错,正是我们想只要的意义。如果你无思就此ThenBy,且还是升序的说话,我们为得:

变更的sql是一律的。与OrderBy、ThenBy对应的降序有OrderByDescending、ThenByDescending。

类似好像挺到了。其实不然,我们大部分景象排序是动态的。比如,我们会越加前端页面不同的操作要求不同字段的两样排序。那我们后台应该怎么开也?

本,这样成功是没问题之,只要您肯。可以这么多或者的论断出无发出痛感格外SB?是的,我们当然发再度好的缓解方案。要是OrderBy可以一直传字符串???

解决方案:

  1. guget下载System.Linq.Dynamic 
  2. 导入System.Linq.Dynamic命名空间
  3. 编写OrderBy的扩充方法

接下来上面又增长又丑的代码可以描绘成:

咱俩看下别的sql:

同咱们纪念要之效用完全符合,是未是发美美哒!!

【注意】:传播的排序字段后面要加排序关键字
asc或desc

何以已一个Promise链?

在片气象下,我们恐怕会见赶上一个于丰富之Promise链式调用,在某个一样步着冒出的错误为咱们完全没必要去运作链式调用后面有的代码,类似下面这样(此处有些去了then/catch里的函数):

new Promise(function(resolve, reject) {
  resolve(42)
})
  .then(function(value) {
    // "Big ERROR!!!"
  })
  .catch()
  .then()
  .then()
  .catch()
  .then()

万一是Big
ERROR!!!的面世给咱们了没必要运行后有的代码了,但链式调用的尾就是来catch,也发then,无论我们是return还是throw,都不可避免的相会进入某一个catch或then里面,那有无出主意于这个链式调用在Big
ERROR!!!的后就停掉,完全无错过履行链式调用后面有回调函数呢?

平等开始遇到是问题的下自己哉百思念不得其解,在网上搜遍了啊不曾结果,有人说得于每个catch里面判断Error的型,如果自己处理不了就算随即throw,也发几其它措施,但连续要对现有代码进行一些变更并且有着的地方都要按部就班这些约定,甚是辛苦。

但当自己于一个实现者的角度看问题时常,确实找到了答案,就是以发生Big
ERROR后return一个Promise,但这个Promise的executor函数什么也非举行,这就算代表这Promise将永久地处pending状态,由于then返回的Promise会直接得到这个永处于pending状态的Promise的状态,于是回到的这个Promise也用一直处在pending状态,后面的代码也便直不会见实施了,具体代码如下:

new Promise(function(resolve, reject) {
  resolve(42)
})
  .then(function(value) {
    // "Big ERROR!!!"
    return new Promise(function(){})
  })
  .catch()
  .then()
  .then()
  .catch()
  .then()

这种艺术看起有些山寨,它吗实在解决了问题。但其引入的一个初题材就是链式调用后面的装有回调函数都心有余而力不足让垃圾回收器回收(在一个指谱的落实里,Promise应该在实行完毕所有回调后删除对所有回调函数的援以给它们会于回收,在前文的兑现里,为了削减复杂度,并从未开这种拍卖),但假如我们无使匿名函数,而是利用函数定义或者函数变量的话,在待频繁行之Promise链中,这些函数也还只生同样客在内存中,不受回收也是好领的。

我们得以拿回来一个哟啊未举行的Promise封装成一个闹语义的函数,以追加代码的可读性:

Promise.cancel = Promise.stop = function() {
  return new Promise(function(){})
}

接下来我们虽足以这么使用了:

new Promise(function(resolve, reject) {
  resolve(42)
})
  .then(function(value) {
    // "Big ERROR!!!"
    return Promise.stop()
  })
  .catch()
  .then()
  .then()
  .catch()
  .then()

看起是免是出语义的大多?

lamdba条件做

要求:根据不同情形询问,可能情况

  1. 查询name=“张三” 的享有学员
  2. 查询name=“张三” 或者 age=18底备学生

贯彻代码:

大凡无是味到了一如既往的臭。下面我们来活组装Lamdba条件。

缓解方案:

就段代码我吧是由网上偷之,具体链接找不顶了。

然后我们的代码可以描绘成:

产生没有发生美美哒一点。然后我们看生成的sql是否是:

Promise链上回来的最后一个Promise出错了怎么处置?

设想如下代码:

new Promise(function(resolve) {
  resolve(42)
})
  .then(function(value) {
    alter(value)
  })

初一收押类似没什么问题,但运行就段代码的语句你晤面发现什么状况也未见面有,既不会见alert出42,也非见面当控制台报错,怎么回事呢。细看最后一实践,alert被由成了alter,那干什么控制台也未尝报错呢,因为alter所在的函数是受保险在try/catch块里的,alter这个变量找不顶即一直扔错了,这个摩擦就正好成了then返回的Promise的rejection
reason。

也就是说,在Promise链的终极一个then里涌出的荒唐,非常不便觉察,有篇指出,可以于装有的Promise链的结尾还助长一个catch,这样差后即能够为破获到,这种办法真的是有效的,但是首先以每个地方还增长几乎等同之代码,违背了DRY原则,其次为相当之繁琐。另外,最后一个catch依然返回一个Promise,除非您能确保是catch里的函数不再出错,否则问题仍然有。在Q中生一个术吃done,把此措施链到Promise链的尾声,它就可知捕获前面未处理的荒谬,这事实上与于每个链后面加上catch没有最好死之界别,只是由于框架来举行了当时起事,相当给其提供了一个未会见错的catch链,我们可如此实现done方法:

Promise.prototype.done = function(){
  return this.catch(function(e) { // 此处一定要确保这个函数不能再出错
    console.error(e)
  })
}

然,能免可知在无加以catch或者done的状况下,也会被开发者发现Promise链最后之错也?答案依然是必然之。

我们得以在一个Promise被reject的上检查是Promise的onRejectedCallback数组,如果其也空,则说明它的错误将无函数处理,这个上,我们用拿错输出及控制台,让开发者可以发现。以下也现实贯彻:

function reject(reason) {
  setTimeout(function() {
    if (self.status === 'pending') {
      self.status = 'rejected'
      self.data = reason
      if (self.onRejectedCallback.length === 0) {
        console.error(reason)
      }
      for (var i = 0; i < self.rejectedFn.length; i++) {
        self.rejectedFn[i](reason)
      }
    }
  })
}

地方的代码对于以下的Promise链也能够处理的慌好:

new Promise(function(){ // promise1
  reject(3)
})
  .then() // returns promise2
  .then() // returns promise3
  .then() // returns promise4

看起,promise1,2,3,4且未曾处理函数,那是未是会见在控制台把这荒唐输出4坏也,并无会见,实际上,promise1,2,3都隐式的出处理函数,就是then的默认参数,各位应该还记then的默认参数最终是让push到了Promise的callback数组里。只有promise4是真的没外callback,因为压根就从来不调用它的then方法。

实在,Bluebird和ES6
Promise都做了类似的拍卖,在Promise被reject但与此同时尚未callback时,把错输出及控制台。

Q使用了done方法来上类似的目的,$q在最新的版被吗加盟了近似之力量。

EF的预热

http://www.cnblogs.com/dudu/p/entity-framework-warm-up.html

Angular里的$q跟其它Promise的交互

诚如的话,我们无见面在Angular里使用外的Promise,因为Angular已经集成了$q,但小上咱们以Angular里需要因此到另外的库(比如LeanCloud的JS
SDK),而这些库或者封装了ES6的Promise,或者是祥和实现了Promise,这时要您当Angular里使用这些库,就有或发现视图跟Model不一起。究其原因,是为$q已经合并了Angular的digest
loop机制,在Promise被resolve或reject时触发digest,而其他的Promise显然是未会见并的,所以若您运行下面这样的代码,视图是免见面共同的:

app.controller(function($scope) {
  Promise.resolve(42).then(function(value) {
    $scope.value = value
  })
})

Promise结束时并无会见触发digest,所以视图没有联手。$q上正有个when方法,它可以将其余的Promise转换成为$q的Promise(有些Promise实现着提供了Promise.cast函数,用于将一个thenable转换为她的Promise),问题虽化解了:

app.controller(function($scope, $q) {
  $q.when(Promise.resolve(42)).then(function(value) {
    $scope.value = value
  })
})

自然为起外的解决方案仍当其余Promise的链条的尾声加一个digest,类似下面这样:

Promise.prototype.$digest = function() {
  $rootScope.$digest()
  return this
}
// 然后这么使用
OtherPromise
  .resolve(42)
  .then(function(value) {
    $scope.value = value
  })
  .$digest()

因运用状况并无多,此处不举行深刻讨论。

count(*)被公用大了邪(Any的用法)

要求:查询是否有名字啊“张三”的学童。(你的代码会咋样形容为?)

先是种植?第二种植?第三种?呵呵,我原先就是是下的首先种植,然后有人说“你count被您用好了”,后来自家怀念了纪念了怎么就受我为此大了呢?直到对比了立即三只报告句子的特性后自明白了。

性的异竟生三百几近倍,count确实给我因此生了。(我眷恋,不止于我一个丁为此异常了咔嚓。)

俺们看上面的Any干嘛的?官方说明是:

本身屡屡读是中文说明,一直无法知晓。甚至早有人吗提出过一样的谜《事实上看不懂MSDN关于
Any
的解说》

因而自己个人掌握呢是“确定集合中是否发生素满足某一样极”。我们来探视any其他用法:

求:查询教过“张三”或“李四”的讲师

贯彻代码:

星星栽方法,以前我会习惯写第一种植。当然我们省那个成了之sql和行效率之后,看法改变了。

频率的差竟有近六倍

咱更比下count:

得出奇怪的定论:

  1. 以导航属性之中用count和以any性能分不甚,反而FirstOrDefault()
    != null的艺术性能最差。
  2. 每当直性判断其中any和FirstOrDefault() !=
    null性能分别不酷,count性能使差之大都。
  3. 故,不管是直接性还是导航属性我们都用any来判定是否是是无比稳妥的。

出错时,是用throw new Error()还是用return Promise.reject(new Error())呢?

此处自己道关键由性能及编码的舒适度角度考虑:

性方面,throw new
Error()会要代码进入catch块里之逻辑(还记得我们管持有的回调都管在try/catch里了咔嚓),传说throw用几近矣会晤影响属性,因为同一但throw,代码就发生或过到不得预知的职位。

唯独考虑到onResolved/onRejected函数是直给担保在Promise实现里的try里,出错后哪怕直接进去了此try对应
的catch块,代码的腾“幅度”相对较小,我认为这里的性质损失可以忽略不记。有会可测试一下。

设若采取Promise.reject(new
Error()),则要结构一个初的Promise对象(里面包含2单数组,4只函数:resolve/reject,onResolved/onRejected),也会见花费一定之时日跟内存。

比方自编码舒适度的角度考虑,出错用throw,正常时用return,可以比较明显的区分出错与常规,throw和return又跟为要字,用来处理相应之图景为展示比较对如(-_-)。另外在一般的编辑器里,Promise.reject不会见为高亮成与throw和return一样的水彩。最后,如果开发者又非喜欢构造出一个Error对象的语,Error的高亮为并未了。

综上,我以为以Promise里发现显式的失实后,用throw抛来荒唐会较好,而不是显式的结构一个被reject的Promise对象。

晶莹剔透标识符

若由于各种缘由我们得写下面这样逻辑的话语

咱得以写成这样更好

看生成的sql就明白了

次栽艺术转变的sql要清得多,性能为又好。

顶尖实践

这里不免再度啰嗦两句最佳实践

1.同一凡是绝不将Promise写成嵌套结构,至于怎么改善,这里就是不多说了

// 错误的写法
promise1.then(function(value) {
  promise1.then(function(value) {
    promise1.then(function(value) {

    })
  })
})

2.二凡是链式Promise要赶回一个Promise,而无单单是布局一个Promise

// 错误的写法
Promise.resolve(1).then(function(){
  Promise.resolve(2)
}).then(function(){
  Promise.resolve(3)
})

EntityFramework.Extended

此间推荐下插件EntityFramework.Extended,看了产,很正确。

顶老的长就是是足以一直批量窜、删除,不用像EF默认的内需事先做询问操作。

有关官方EF为什么没有提供这样的支撑就非亮堂了。不过用EntityFramework.Extended需要专注以下几点:

  1. 只支持sql server
  2. 批量修改、删除时无克兑现业务(也不怕是发了杀不能够回滚)
  3. 靡联级删除
  4. 不能同EF一起SaveChanges
    (详见)

http://www.cnblogs.com/GuZhenYin/p/5482288.html

在这个正个问题EntityFramework.Extended并无是说非可知回滚,感谢@GuZhenYin园友的指正(原谅自己事先并未动手测试)。

注意:需要NuGet下载EntityFramework.Extended,
并导入命名空间: using
EntityFramework.Extensions ;

测试代码如下:(如果注释掉手抛大代码是得一直更新至数据库的)

using (var ctxTransaction = db.Database.BeginTransaction())
{
    try
    {
        db.Teachers.Where(t => true).Update(t => new Teacher { Age = "1" });

        throw new Exception("手动抛出异常");

        ctxTransaction.Commit();//提交事务
    }
    catch (Exception)
    {
        ctxTransaction.Rollback();//回滚事务
    }
}

Promise相关的convenience method的实现

请到这里查Promise.race,
Promise.all, Promise.resolve,
Promise.reject等办法的现实贯彻,这里虽未具体讲了,总的来说,只要then的兑现是从未问题之,其它具有的措施都得老有利于的依赖then来落实。

从今定义IQueryable扩展方法

 最后整理下于定义之IQueryable的恢弘。

 

 

补充1:

First和Single的区别:前者是TOP(1)后者是TOP(2),后者如果查询到了2条数据则抛出异常。所以在必要的时候使用Single也不会比First慢多少。

补充2: 

都打包nuget提供第一手装 Install-Package
Talk.Linq.Extensions 或nuget搜索 Talk.Linq.Extensions 

https://github.com/zhaopeiym/Talk/wiki/Talk.Linq.Extensions_demo

 

结束:

源码下载:http://pan.baidu.com/s/1o8MYozw

正文为共同到《C#基础知识巩固系列》

欢迎热心园友葡京网上娱乐场补充!

结语

最终,如果你看这篇稿子针对性而富有帮助,欢迎分享给您的朋友或者组织,记得注明有处哦~

初稿链接:https://github.com/xieranmaya/blog/issues/3