Promise太重要了,可以说是改变了JavaScript开发体验重要内容之一。而Promise也可以说是现代Javascript中极为重要的核心概念,所以理解Promise/A+规范,理解Promise的实现,手写Promise就显得格外重要。如果要聊Promise就要从回调函数聊到回调地狱,再聊到同步异步,最终聊到Promise、async await。但是我们这篇文章,目的是手写Promise,这些前置知识如果大家不了解的话,希望可以去补充一下。那你可能会说了,我他妈不懂你在说啥,我就是想手写Promise,不行么?大佬~~那肯定是没问题的。好了,废话不多说,咱们开始吧。

一、实现Promise的基本结构

  最开始的部分,我们不得不去看看Promise/A+规范,因为我们所实现的、手写的代码,其实都是根据规范,一步一步来实现的。规范的地址,附在文末。

  整个Promise/A+规范有三个部分:术语、要求、说明。术语部分介绍了核心字段的关键性解释,要求部分基本上就是对整个Promise实现的要求,说明部分,则对Promise的实现提供了一些特殊场景的补充说明。

  首先,我们需要简单了解下Promise的基本概念。Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。与Promise交互的主要方式是通过它的then方法,该方法会注册一个回调函数,用来接收Promise的最终结果,这个结果可能是Promise的最终值,也可能是一个失败的原因。也就是Promise的resolve和reject呗~

  Promise/A+规范详细说明了该then方法的行为,所有符合Promise/A+规范所实现的Promise都可以基于此来提供一个可以互相操作的基础。因此,该规范应该是十分稳定的。也就是说,该Promise/A+规范并不会随意迭代,提供了一个长期稳定不变的规范版本。当然,也可能会有小的修改,但是一定是经过深思熟虑的。额~这些都是废话。重点就是最开始那一句:详细说明了then方法的行为。记住这句话!记住这句话!记住这句话!因为我们后面所做的一切,除了基本结构和参数,所有的一切都是围绕着then展开的。

  最后,核心的Promise/A+规范并不会去管你怎么实现,而是选择专注于提供可互操作的then方法。换句话说,我不管你写怎么实现Promise,我只关注你应该怎么实现then方法的可操作性。

  好了,基本的背景,我们了解了,我们之前说了,整个Promise/A+规范有三部分,我们先来看看第一部分术语,并实现这第一部分。

  1. “promise”是一个function或者object,它的行为要符合本规范。
  2. “thenable”是一个function或者object,它定义了then方法。(啥意思呢,其实就是:thenable是一个可以调用then方法的function或者object,再换句话说,就是我们new Promise().then的这个new Promise()呗,你咋实现我不管,有就行。)
  3. “value”是任何合法的值(包括thenable、promise或者undefined)。
  4. “exception”是使用throw语句抛出的值。
  5. “reason”是一个用来表示Promise为什么被reject的原因,的值。

  就这些,但是我稍微解释下,promise是指Promise这个整体,它可以是一个构造函数,也可以是个对象。如果Promise是个构造函数的话,那么thenable,其实就是Promise这个构造函数的实例,这个实例要有一个then方法。继续,exception就是异常处理,通过try catch语句来抛出错误。那么最后,value和reason分别对应了resolve和reject的参数。

  巴巴了这么多,我们先来写一点代码吧,不然长篇大论确实有点枯燥。

  前面总结了一下Promise基本背景,还有基本的术语,我们这一小节就来实现基于此的代码,但是我先总结一下哈,我们要实现的到底包括哪些内容。

  首先,我们要实现一个Promise类,这个类会提供一个实例方法也就是then方法。然后,我们还要有两个值:value和reason。最后,我们还要处理抛出的异常。没啦~一句话概括,精炼!

 1 class Promise {
2 constructor(excutor) {
3 this.value = undefined;
4 this.reason = undefined;
5 const resolve = (value) => {};
6 const reject = (reason) => {};
7 try {
8 excutor(resolve, reject);
9 } catch (err) {
10 reject(err);
11 }
12 }
13 then(onFulfilled, onRejected) {}
14 }

  简单不,其实啥也没有,就是按照之前我们描述的规范,来一步一步实现的。我简单再总结下,首先我们声明了一个Promise类,然后这个类有一个实例方法then,这个then方法接收两个回调函数(后面我们会完善的),然后整个类的构造函数部分,声明了一个value和reason,分别就是该Promise构造函数对应结果状态的值。最后,我们通过try…catch语句执行传递进来的回调excutor,并把resolve和reject方法作为excutor的回调。这里稍微有点绕,要捋一下噢。

  那么,我们再根据使用时的场景,来巩固一下上面的代码,通常,我们在使用Promise的时候,是这样的:

1 const p1 = new Promise((resolve, reject) => {});

  我们看,使用的时候,我们会给Promise这个类传过去一个方法,OK,这个方法就是我们Promise类中excutor。而这个executor又传回去了两个函数参数,可以让我们在new Promise时传递的executor的内部去调用:

1 const p1 = new Promise((resolve, reject) => {
2 if(true){
3 resolve("success")
4 } else {
5 reject("fail")
6 }
7 });

  这样我们的value和reason就传递给了constructor中的resolve和reject。最后,还有个实例方法then:

1 p1.then(
2 (value) => {
3 console.log("成功", value);
4 },
5 (reason) => {
6 console.log("失败", reason);
7 }
8 );

  这个then方法有两个参数,分别是成功的回调函数和失败的回调函数,也就是分别代表了Promise类中的实例的then方法的onFulfilled, onRejected。那么基本的结构我们就写完了,但是现在我们写的这个Promise肯定是没法用的。所以,我们还得继续往下,看看规范怎么要求的,我们按照要求再来完善。

二、完成基本的Promise

  我们前面完成了基本的结构,但是那些代码还缺了一些内容,这一小节,我们就来根据后面的Promise/A+规范,也就是要求部分,来实现、完善后面的代码。首先,规范就对Promise的状态,进行了详细的描述。

  promise必须处于以下三种状态之一:pending, fulfilled, or rejected。也就是待处理、已完成或已拒绝。

    1. 当处于pending状态时:

    • 可以转换到已完成(fulfilled)或者已拒绝(rejected)状态。

    2. 当处于fulfilled状态时:

    • 不可以改变状态。
    • 必须有一个不能改变的value。

    3.  当处于rejected状态时:

    • 不可以改变状态。
    • 必须有一个不能改变的reason。

  OK,这就是关于Promise的状态的描述。其实上面的说明很好理解,就是我们要给Promise这个类维护一个status字段,这个status有三个状态常量。一旦确定了status的结果,也就是status如果不是pending的话,就不能再修改status的状态了。

  我们继续,针对then方法的详细描述,当然,我们这个阶段,只需要其中的一部分。首先,Promise必须提供一个then方法来让使用者可以访问当前或最终状态的value或reason。其次,Promise的then方法接受两个参数:

promise.then(onFulfilled, onRejected)

  诶嘿!就是我们上面写的噢!继续~注意,以下的列表标记,完全抄袭自Promise/A+规范,方便大家查找。

  2.2.1 onFulfilled和onRejected都是可选参数

    2.2.1.1  如果onFulFilled不是函数,则忽略。

    2.2.1.2  如果onRejected不是函数,则忽略。

  2.2.2  如果onFulfilled是一个函数

    2.2.2.1  onFulfilled必须在promise已经是fulfilled的状态后调用,并且把promise的value作为它的第一个参数。

    2.2.2.2  在promise已经是fulfilled状态之前该方法不能被调用。

    2.2.2.3  该方法只能被调用一次。

  2.2.3  如果onRejected是一个函数

    2.2.3.1  onRejected必须在promise已经是rejected的状态后调用,并且把promise的reason作为它的第一个参数。

    2.2.3.2  在promise已经是rejected状态之前该方法不能被调用。

    2.2.3.3  该方法只能被调用一次。

  2.2.4  onFulfilled或者onRejected必须在执行上下文栈只有平台代码的时候才可以被调用。

  2.2.5  onFulfilled或者onRejected必须作为函数被调用(即没有this值)。

  OK,这一段规范翻译,其中尤其要关注的是2.2.4和2.2.5。

  首先,2.2.4是什么意思呢?平台代码,就是指环境、引擎、或者promise实现的代码,也就是说在onFulfilled或者onRejected被调用的时候,只能是在执行上下文栈中的最底层,它的前面要么是全局、要么是另一个符合规范的promise。在实践中,这样可以确保在调用事件环之后异步执行onFulfilled或者onRejected回调。这可以通过宏任务(比如 setTimeout 或者setImmediate)或微任务(MutationObserver或者process.nextTick)机制来实现。由于promise的实现会被当作平台代码,所以它本身可能包含一个任务调度队列或者一个调用处理程序的“蹦床”。这里的蹦床可以理解成一种转换函数,比如递归可能会导致栈溢出,那么可以通过蹦床函数把递归转换成循环,绕过JS的栈溢出检测机制。当然你也可能会写成死循环,那就是你代码的问题了。

  其次,2.2.5比较容易理解,就是指你实现的onFulfilled或者onRejected只能是一个函数,并且它内部的this如果在严格模式下是undefined,在非严格模式,则是全局对象。换句话说,也就是你的onFulfilled或者onRejected不会被任何对象或者函数调用,不归属于任何对象或者函数。

  理解了这段规范了吧?那么我们开始手写代码吧。

  首先,根据规范,我们要加入三个状态常量,并且Promise的初始状态肯定是pending:

 1 const PEDNING = "PENDING";
2 const FULFILLED = "FULFILLED";
3 const REJECTED = "REJECTED";
4 class Promise {
5 constructor(excutor) {
6 this.status = PENDING;
7 this.value = undefined;
8 this.reason = undefined;
9 const resolve = (value) => {};
10 const reject = (reason) => {};
11 try {
12 excutor(resolve, reject);
13 } catch (err) {
14 reject(err);
15 }
16 }
17 then(onFulfilled, onRejected) {}
18 }

  额外的,我们还要维护一个队列,这个队列用来当我们调用resolve或者reject的时候,触发我们传给then方法的函数参数。我们还要完善resolve和reject方法,以及then方法:

 1 const PENDING = "PEDNING";
2 const FULFILLED = "FULFILLED";
3 const REJECTED = "REJECTED";
4 class Promise {
5 constructor(excutor) {
6 this.status = PENDING;
7 this.value = undefined;
8 this.reason = undefined;
9 this.onResolvedCallbacks = [];
10 this.onRejectedCallbacks = [];
11 const resolve = (value) => {
12 if (this.status === PENDING) {
13 this.value = value;
14 this.status = FULFILLED;
15 this.onResolvedCallbacks.forEach((cb) => cb(this.value));
16 }
17 };
18 const reject = (reason) => {
19 if (this.status === PENDING) {
20 this.reason = reason;
21 this.status = REJECTED;
22 this.onRejectedCallbacks.forEach((cb) => cb(this.reason));
23 }
24 };
25 try {
26 excutor(resolve, reject);
27 } catch (err) {
28 reject(err);
29 }
30 }
31 then(onFulfilled, onRejected) {
32 if (this.status === FULFILLED) {
33 onFulfilled(this.value);
34 }
35 if (this.status === REJECTED) {
36 onRejected(this.reason);
37 }
38 if (this.status === PENDING) {
39 this.onResolvedCallbacks.push(onFulfilled);
40 this.onRejectedCallbacks.push(onRejected);
41 }
42 }
43 }

  我们来看下上面的代码,第9、10行,我们分别维护了一个数组,当然我们在promise仍旧是pending状态的时候调用then方法,就会把这两个状态的回调函数缓存起来。等到我们调用resolve或者reject确认了promise的状态时,就会去调用对应的缓存队列执行回调。

  resolve和reject方法十分简单,绑定value或者reason,改变状态,然后执行对应缓存的回调函数。而then方法的处理目前也并不复杂,根据状态去调用对应回调或者缓存回调。

  注意,之前我们翻译规范的时候还说过要确保在调用事件环之后异步执行onFulfilled或者onRejected回调。这个我们后面再实现,所以现在还都是同步代码。我们来测试一下我们的代码执行效果吧:

 1 const p1 = new Promise((resolve, reject) => {
2 resolve("success");
3 });
4
5 p1.then(
6 (value) => {
7 console.log("成功", value);
8 },
9 (reason) => {
10 console.log("失败", reason);
11 }
12 );
13
14 const p2 = new Promise((resolve, reject) => {
15 reject("fail");
16 });
17
18 p2.then(
19 (value) => {
20 console.log("成功", value);
21 },
22 (reason) => {
23 console.log("失败", reason);
24 }
25 );

  我们来分析下这段代码是如何执行的。首先,当我们new Promise的时候就会执行Promise这个类的constructor,此时生成了部分实例字段和resolve、reject方法。并且执行了excutor方法,那么由于我们在方法内部调用resolve方法,也就相当于constructor是这样执行的:

1 try {
2 function excutor(resolve,reject){
3 resolve("success")
4 }
5 excutor(resolve, reject);
6 } catch (err) {
7 reject(err);
8 }

  那么此时,我们已经先走了resolve方法,也就是先确定了promised状态,再强调一下,现在都是同步的噢,然后,当我们后面再去调用then方法的时候,promise的状态已经是确定的了,所以不会去走缓存,直接走了

if (this.status === FULFILLED) {
onFulfilled(this.value);
}

  这段代码,于是就执行了传给then的onFulfilled状态的回调。p2的例子也是一样的。

  那么我们继续再来看个例子:

 1 const p1 = new Promise((resolve, reject) => {
2 setTimeout(() => {
3 resolve(1);
4 }, 1000);
5 });
6
7 p1.then(
8 (value) => {
9 console.log(value);
10 },
11 (reason) => {
12 console.log(reason);
13 }
14 );

  这个例子的执行结果是什么?我先不说答案,来捋一下,首先我们执行了excutor,但是此时我们并没有调用resolve,因为是异步的,所以要在一秒后才去调用resolve,那么此时executor就执行完了,然后我们去执行then方法,此时状态还没确定,所以走then的时候就走了这段代码:

1 if (this.status === PENDING) {
2 this.onResolvedCallbacks.push(onFulfilled);
3 this.onRejectedCallbacks.push(onRejected);
4 }

  缓存了起来,那么等到一秒后,调用了resolve,由于此时还是pending状态,所以resolve就走了其内部的逻辑:

1 const resolve = (value) => {
2 if (this.status === PENDING) {
3 this.value = value;
4 this.status = FULFILLED;
5 this.onResolvedCallbacks.forEach((cb) => cb(this.value));
6 }
7 };

  所以一秒后才会打印出1。

三、完善Promise的then方法

  那么继续,我们要进入下一个阶段了,之前的阶段我们解读(就是把规范复制过来翻译翻译)的规范还欠了点债,就是异步处理。那么这一小节,我们继续翻译剩下的规范,并且把欠大家的债还上。

  2.2.6  then方法可以被同一个promise调用多次

    2.2.6.1  当promise是fulfilled状态的时候,所有相关的onFulfilled回调函数必须按照then方法调用的顺序执行。

    2.2.6.2  当promise是rejected状态的时候,所有相关的onRejected回调函数必须按照then方法调用的顺序执行。

  2.2.7  then方法必须返回一个promise

promise2 = promise1.then(onFulfilled, onRejected);

    2.2.7.1  如果onFulfilled或者onRejected的其中一个返回了一个值,x。那么要运行:Promise Resolution Procedure [[Resolve]](promise2, x)。

    2.2.7.2  如果onFulfilled或者onRejected其中一个抛出了一个错误,e。promise2也必须变成使用e作为reason的拒绝状态。

    2.2.7.3  如果onFulfilled不是一个函数,并且promise1已经是fulfilled状态,promise2也必须变成fulfilled状态并且使用和promise1一样的value作为值。

    2.2.7.4  如果onRejected不是一个函数,并且promise1已经是rejected状态,promise2也必须变成rejected状态并且使用和promise1一样的reason作为错误信息。

  OK,这就是我们这个阶段要实现的规范。其中有些内容还是要解释下的。首先就是让人疑惑的Promise Resolution Procedure [[Resolve]](promise2, x)这个东西我特意没有翻译,因为你需要把它看作一个整体,现在这个阶段,你可以把它理解成一段要处理特定逻辑的代码块

  那么然后就是2.2.7.2到2.2.7.4,实际上只说了一句话,then方法必须返回一个promise,并且一旦该promise的状态已经确定,后续的promise也一定是同样的状态不得更改,且要把源promise的value或reason依次向后传递。也就是所谓的值穿透,听起来高大上,实际上没啥复杂的概念。

  我们接下来写代码吧。哦对,2.2.6其实我们上一小节已经写完了,就是那个数组,每次调用then如果promise是pending状态,就会往两个数组中依照then调用的顺序依次往里添加回调。所以,我们这一小节,实际上需要处理的核心内容,就是then方法,或者,我可以在这里确切的告诉大家,Promise的核心就是这个then方法,Promise中核心的核心是resolvePromise方法,接下来你就知道我为什么这么说了。

  constructor的代码暂时没有变化,这里就不再复制了,我们仅特别重要的关注then的变化,那么第一件要做的就是then方法会返回一个promise:

 1 then(onFulfilled, onRejected) {
2 let p = new Promise((resolve, reject) => {
3 if (this.status === FULFILLED) {
4 onFulfilled(this.value);
5 }
6 if (this.status === REJECTED) {
7 onRejected(this.reason);
8 }
9 if (this.status === PENDING) {
10 this.onResolvedCallbacks.push(onFulfilled);
11 this.onRejectedCallbacks.push(onRejected);
12 }
13 })
14 return p;
15 }

  啥也没干哈,就是返回了个新的promise,其实这也就是promise的链式调用。那问题来了,为啥我要把then的内容用一个新的promise包裹起来呢?既然要返回个promise,那我直接在代码的最后面,比如这样:

 1 then(onFulfilled, onRejected) {
2 if (this.status === FULFILLED) {
3 onFulfilled(this.value);
4 }
5 if (this.status === REJECTED) {
6 onRejected(this.reason);
7 }
8 if (this.status === PENDING) {
9 this.onResolvedCallbacks.push(onFulfilled);
10 this.onRejectedCallbacks.push(onRejected);
11 }
12 let p = new Promise((resolve, reject) => {
13 })
14 return p;
15 }

  那我这样不就可以了么?别忘了,我们还要把promise1的结果,传递给promise2,所以我们通过再生成一个新的promise2的内部去执行我们的回调。

  那么我们要如何把promise1的结果传给下一层呢?我们回头看下2.2.7.1,结果叫做x:

 1 then(onFulfilled, onRejected) {
2 let p = new Promise((resolve, reject) => {
3 if (this.status === FULFILLED) {
4 setTimeout(() => {
5 try {
6 let x = onFulfilled(this.value);
7 resolvePromise(p, x, resolve, reject);
8 } catch (error) {
9 reject(error);
10 }
11 }, 0);
12 }
13 if (this.status === REJECTED) {
14 setTimeout(() => {
15 try {
16 let x = onRejected(this.reason);
17 resolvePromise(p, x, resolve, reject);
18 } catch (error) {
19 reject(error);
20 }
21 }, 0);
22 }
23 if (this.status === PENDING) {
24 this.onResolvedCallbacks.push(() => {
25 setTimeout(() => {
26 try {
27 let x = onFulfilled(this.value);
28 resolvePromise(p, x, resolve, reject);
29 } catch (error) {
30 reject(error);
31 }
32 }, 0);
33 });
34 this.onRejectedCallbacks.push(() => {
35 setTimeout(() => {
36 try {
37 let x = onRejected(this.reason);
38 resolvePromise(p, x, resolve, reject);
39 } catch (error) {
40 reject(error);
41 }
42 }, 0);
43 });
44 }
45 });
46 return p;
47 }

  OK,这就是这一小节完整的then方法的部分了,甚至是整个promise实现的then方法也基本上跟这个差不多了,我们先来看当调用then方法的时候已经是fulfilled状态了的话,我们会获取到我们传给promise1的onFulfilled回调的结果作为x,这就是我们之前规范里所说的x,然后它走了一个resolvePromise,传了四个参数,并且把我们外面的那个p传了进去,那么假设,我没写外面的setTimeout,我能把p传给resolvePromise方法么?不能!!!因为我们想要在p还未生成之前就传给了内部的resolvePromise方法,这时候还没有p呢,所以我们利用事件循环机制,包了一层setTimeout,这样等到下一个tik整个p生成,我们就可以传给resolvePromise了。

  那么我们注意,无论在哪一个状态中,两种结果状态也就是fulfilled或rejected时,只要拿到了结果,就会走resolvePromise方法,只有try……catch报错了,才会直接抛出异常。那么这里的resolvePromise,其实就是我们前面没有翻译的那一段逻辑:Promise Resolution Procedure [[Resolve]](promise2, x),就这个。换句话说promise1的任何确定的状态结果,对于promise2来说都是要去resolve的,除非执行promise2的时候报错了。

  简单总结下:

  • then方法要返回个promise2。
  • then方法的成功或失败的回调函数的结果都是x。
  • x会传给resolvePromise做后续的处理。
  • 通过setTimeout的异步来使得resolvePromise可以获取到then方法内新生成的promise2。
  • 无论是失败还是成功,结果都是x,都会传给resolvePromise去做处理,同样的try……catch的报错会走promise1的reject。

四、实现完整的Promise

  那么这一小节,我们要实现完整的Promise,其实就是完善resolvePromise方法啦。在写代码之前,我们还是要来解读一下规范,我们就是照着规范写代码啦。

  Promise Resolution Procedure是一个会接收promise和x作为参数的一个抽象操作,我们把它标记为:[[Resolve]](promise, x)。如果x的表现形式是thenable的,也就是说如果x的结果是一个promise的话,那么假设x的行为与promise相似,则会试图使promise采用x的状态。

  这种thenable的处理允许promise之间的互相操作,只要这些promise的实现符合本规范的要求。它同样允许符合本规范的实现使用合理的then方法“同化”不符合的实现。

  [[Resolve]](promise, x)程序需要遵循以下步骤:

  2.3.1  如果promise和x引用自同一个对象,那么则抛出一个TypeError作为reason的reject状态。

  2.3.2  如果x是一个promise,则采用它的状态。通常,只有当x的实现是符合本规范的要求时,才会知道它是不是一个真正的承诺。本条规则允许使用特定于实现的方法来采用已知符合promise的状态。

    2.3.2.1  如果x是pending的状态,那么promise也必须保持pending状态,直到x的状态已变更为fulfilled或者rejected。

    2.3.2.2  如果x是fulfilled状态,那么就把promise的状态变更为fulfilled并使用与x一样的value。

    2.3.2.3  如果x是rejected状态,那么就把promise的状态变更为rejected并使用与x一样的reason。

  2.3.3  如果x是一个对象或者函数

    2.3.3.1  声明一个then变量存储x.then方法。这样做,其实就是为了缓存一下x.then的引用,那么当我们后续测试该引用,调用该引用的时候,都避免了多次调用x.then的访问。这些预防措施对于确保访问器属性的一致性非常重要,因为访问器属性的值可能在两次检索之间发生变化。

    2.3.3.2  如果我们在执行x.then后抛出了一个异常e,那么promise状态应修改为以e作为参数的rejected状态。

    2.3.3.3  如果then是一个函数,那么则调用then.call,把x作为call的第一个参数(也就是作为then的this),resolvePromise作为第二个参数,rejectPromise作为第三个参数,其中:

      2.3.3.3.1  如果resolvePromise被调用,并且使用y作为value,就执行[[Resolve]](promise, y)。

      2.3.3.3.2  如果rejectPromise被调用,并且使用r作为reason,就让promise的状态变为rejected。

      2.3.3.3.3  如果同时调用resolvePromise和rejectPromise或多次调用同一个,则第一个调用优先,并且任何进一步的调用都将被忽略。

      2.3.3.3.4  如果调用then方法抛出了一个错误e。

        2.3.3.3.4.1  如果resolvePromiserejectPromise已经被调用了,那么则忽略它。

        2.3.3.3.4.2  否则,把promise的状态变更为rejected,e作为reason。

    2.3.3.4  如果then不是一个一个函数,那么则把promise的状态变更为fulfilled,把x作为value。

  2.3.4  如果x不是一个函数或者对象,那么则把promise的状态变更为fulfilled,把x作为value。

  如果一个promise被一个参与循环的thenable链中的thenable所resolved,这样[[Resolve]](promise,thenable)的递归性质将最终导致再次调用[[Resolve]](promise,thenable),遵循上诉算法将导致无限递归。本规范鼓励但是并不要求一定要去检测这种无限递归,如果实现的话,那么此时promise的状态应该变成rejected并提供有效的错误信息作为reason。

  从实现上来说,不应该对可调用链的深度做任何限制,并假设超出该限制,递归将是无限的。只有确定的循环调用才会抛出TypeError。如果遇到无限的不同thenable链,则无限递归是正确的。

  好吧,这规范巴巴了好多。我建议要看一遍!我们继续上一小节的内容,去完善resolvePromise方法。

  首先2.3.1说了如果promise和x引用自同一个对象,那么抛出错误,这个简单,就这样被:

1 function resolvePromise(promise, x, resolve, reject) {
2 if (promise == x) {
3 return reject(
4 new TypeError("Chaining cycle detected for promise #<Promise>")
5 );
6 }
7 }

  那这样的场景什么时候才会出现呢?为什么x和promise不能相等呢?我们先来看个例子,首先我们在判断条件中添加一个打印,确定走到这个逻辑了:

1   if (promise == x) {
2 console.log("进来了");
3 return reject(
4 new TypeError("Chaining cycle detected for promise #<Promise>")
5 );
6 }

  然后例子是这样的:

 1 const p1 = new Promise((resolve, reject) => {
2 resolve("success");
3 });
4
5 let p2 = p1.then((res) => {
6 return p2;
7 });
8 p2.then(
9 (value) => {
10 console.log("成功", value);
11 },
12 (reason) => {
13 console.log("失败", reason);
14 }
15 );

  执行这段代码,打印结果如下:

进来了
失败 TypeError: Chaining cycle detected for promise #<Promise>

  说明我们的代码没问题,那么我们得来分析下这段代码是如何执行的。在声明p1的时候,我们直接执行了excutor的resolve,所以此时的promise就已经是fulfilled的状态了。当我们再去调用then方法的时候,then方法会返回个promise,此时由于已经是fulfilled状态了,所以会命中if (this.status === FULFILLED)条件,那么此时的x就是调用onFulfilled方法的结果,这个x最终的结果就是p2,所以此时resolvePromise(p, x, resolve, reject)方法中的p和x是同一个,那么在Promise内部,我们要等待p2的执行结果,那么此时p2就即是执行的过程,又是等待的结果,所以p2永远都不会处于结果状态,于是我们要特殊处理这样无法得到最终结果的情况,reject出去。理解了吧?

  我们继续,再往后是2.3.2的解释,这块的解释我们在写代码的时候可以不去考虑,因为在写2.3.3的时候,可以覆盖到2.3.2的逻辑。那么:

 1 function resolvePromise(promise, x, resolve, reject) {
2 if (promise == x) {
3 console.log("进来了");
4 return reject(
5 new TypeError("Chaining cycle detected for promise #<Promise>")
6 );
7 }
8 if ((typeof x === "object" && x !== null) || typeof x === "function") {
9 } else {
10 resolve(x);
11 }
12 }

  着三行代码,就是2.3.3和2.3.4的逻辑框架,如果x是一个对象且不是null或者x是一个函数,那么要走一段逻辑,如果不符合这个条件的话说明是一个普通值,直接resolve就好了。这块很好理解,跟规范描述的一模一样。

  继续,2.3.3.1和2.3.3.2,当我们去取x.then的时候,可能会有报错,所以我们要try……catch处理一下:

1 if ((typeof x === "object" && x !== null) || typeof x === "function") {
2 try {
3 let then = x.then
4 } catch (error) {
5 reject(error)
6 }
7 } else {
8 resolve(x);
9 }

  就是这样。继续2.3.3.3:

 1 if ((typeof x === "object" && x !== null) || typeof x === "function") {
2 try {
3 let then = x.then;
4 if (typeof then === "function") {
5 then.call(
6 x,
7 (y) => {
8 resolve(y)
9 },
10 (r) => {
11 reject(r);
12 }
13 );
14 } else {
15 resolve(x);
16 }
17 } catch (error) {
18 reject(error);
19 }
20 } else {
21 resolve(x);
22 }

  我们看这段代码,跟2.3.3.3所说是一模一样的。我们调用then方法,把x作为this并且传了onFulfilled, onRejected两个函数作为参数,那么基本的核心逻辑我们就处理完了,但是还要补全一些细节,比如2.3.3.3.3所说,如果多次调用,只取第一次的,后面的调用都应该被忽略,那这个逻辑我们要怎么处理呢?再比如2.3.3.3.1所说,then在call的时候,如果resolvePromise被调用,并且使用y作为value,就执行[[Resolve]](promise, y),而不是像我们上面那样,直接resolve就完事了。所以我们要把resolve(y)修改成resolvePromise(promise, y, resolve, reject)。因为我们可能再返回一个promise,直到最后不是一个promise,也就是规范中所说的,允许任意递归调用。

  那么我们继续处理2.3.3.3.3这种情况:
 1 if ((typeof x === "object" && x !== null) || typeof x === "function") {
2 let called = false;
3 try {
4 let then = x.then;
5 if (typeof then === "function") {
6 then.call(
7 x,
8 (y) => {
9 if (called) return;
10 called = true;
11 resolvePromise(promise, y, resolve, reject);
12 },
13 (r) => {
14 if (called) return;
15 called = true;
16 reject(r);
17 }
18 );
19 } else {
20 resolve(x);
21 }
22 } catch (error) {
23 if (called) return;
24 called = true;
25 reject(error);
26 }
27 } else {
28 resolve(x);
29 }

  我们看代码,其实处理起来也并不复杂,我们在执行的时候,添加了一个flag,只要走到了某一个会修改promise状态的逻辑中,就会修改flag的状态,后续再调用就不会再去走逻辑代码了。也就是一旦状态确定,后续无论怎么调用,都是之前确定的状态,无法更改。

  那么其实到现在,核心的代码基本上就都完事了,其实你看,核心的代码其实就是resolvePromise这个方法,剩下的,我们还需要处理一点细节,比如2.2.1,onFulfilled, onRejected都是可选参数。我们稍微来处理一下then方法:

 1 then(onFulfilled, onRejected) {
2 onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) => v;
3 onRejected =
4 typeof onRejected === "function"
5 ? onRejected
6 : (e) => {
7 throw e;
8 };
9 // 后面的略了
10 }

  看到没,代码很简单,如果没传,我们就自己造一个就好了。最后我们就写完了这个Promise的实现,完整代码可在:https://github.com/zakingwong/zaking-js-advanced/tree/promise这里查看。

  那么最后,我们还可以使用社区的工具库,来测试下我们所写的代码是否符合规范。这个我就不多说了,具体可以参考gitHub上的代码。

  既然代码我们都写完了,来玩两个例子吧。

 1 const zp1 = new ZakingPromise((resolve, reject) => {
2 resolve("ok");
3 }).then((data) => {
4 return new Promise((resolve, reject) => {
5 setTimeout(() => {
6 resolve(data);
7 }, 1000);
8 });
9 });
10
11 zp1.then(
12 (data) => {
13 console.log(data, "zp1-resolve");
14 },
15 (err) => {
16 console.log(data, "zp1-reject");
17 }
18 );

  这里的ZakingPromise是我们自己手写的Promise,Promise就是ES6的Promise。我们来分析下这段代码,其实并不难噢。首先,ZakingPromise直接resolve出去了一个ok字符串,那么当我们调用then的时候,状态已经是fulfilled了,此时then里面执行的代码是这样的:

 1 let p = new Promise((resolve, reject) => {
2 if (this.status === FULFILLED) {
3 setTimeout(() => {
4 try {
5 let x = onFulfilled(this.value);
6 resolvePromise(p, x, resolve, reject);
7 } catch (error) {
8 reject(error);
9 }
10 }, 0);
11 }
12 }
13 return p

  pending和rejected都跟我没关系。就执行了上面这点代码,那么这里的onFulfilled就是我们传进来的这段代码:

1 (data) => {
2 return new Promise((resolve, reject) => {
3 setTimeout(() => {
4 resolve(data);
5 }, 1000);
6 });
7 }

  它返回了一个new Promise,所以此时的x可以理解为:

let x = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok');
}, 1000);
});

  OK,那么我们继续,要去走resolvePromise这段代码了。那么resolvePromise判断逻辑就不走了哈,它肯定是个函数然后获取x.then,并且x.then肯定也是函数。那么里面的逻辑就相当于是这样执行的:

 1 new Promise((resolve, reject) => {
2 setTimeout(() => {
3 resolve("ok");
4 }, 1000);
5 }).then(
6 (y) => {
7 if (called) return;
8 called = true;
9 resolvePromise(promise, y, resolve, reject);
10 },
11 (r) => {
12 if (called) return;
13 called = true;
14 reject(r);
15 }
16 );

  这样是不是就很好理解了,那么我们就得去分析下上面的代码,还没调用then是如何执行的,就很简单了对吧,在Promise内部由于调用then的时候还没resolve,之前咱们分析过,所以then方法内存了两个缓存数组,当1秒后调用了resolve("ok")的时候,then里的onFulfilled回调就执行了,此时的y就是“ok”。注意,这里我省略了那个调用call时候的x,也就是此时then方法内部的this只想,那这个x、也就是this就是指这个new Promise他自己。

  于是我们再走这段代码:

1 zp1.then(
2 (data) => {
3 console.log(data, "zp1-resolve");
4 },
5 (err) => {
6 console.log(data, "zp1-reject");
7 }
8 );

  就走了zp1的onFulfilled回调,于是就打印了“ok zp1-resolve”。行吧,例子就这一个了吧暂时,大家要去多练练例子啊,把复杂的情况都梳理梳理。

五、实现Promise的其他方法

   诶?我记得Promise还有catch方法、all方法、还有race、还有finally,这些方法你咋没写呢?嗯,首先前四章所有翻译的内容就是Promise规范的所有内容,不信的话我在文末贴出了Promise/A+规范的地址,也就是说规范中压根就没有这些方法,这些方法都是ES6额外实现的,那么接下来我们就来实现下这些方法。

1、Promise.resolve、Promise.reject和Promise.catch方法的实现

  我们先来看下Promise.resolve和Promise.reject,其实实现起来很简单哈,我们先写个例子:

1 Promise.resolve("ok").then((data) => {
2 console.log(data, "resolve");
3 });

  我们看,调用Promise.resolve直接就返回了个promise的fulfilled状态,再去调用then的成功的回调,所以实现起来就是这样的:

1 static resolve(value) {
2 return new Promise((resolve, reject) => {
3 resolve(value);
4 });
5 }

  前面的那些代码我就不写了哈,但是这样还没完,我们再看个例子:

1 Promise.resolve(
2 new Promise((resolve, reject) => {
3 setTimeout(() => {
4 resolve("Zaking");
5 }, 1000);
6 })
7 ).then((data) => {
8 console.log(data, "resolve----");
9 });

  你猜,按照我们之前实现的代码,这个打印的结果是什么。

Promise {
status: 'PENDING',
value: undefined,
reason: undefined,
onResolvedCallbacks: [],
onRejectedCallbacks: []
} resolve----

  为什么会这样呢?按道理不应该是Zaking这个字符串么?ES6实现的是这样的,但是咱们之前的代码并没有做这个的处理,其实这里的resolve方法,不就是包裹了一层Promise后执行了resolve嘛,所以当我们调用Promise.resolve的时候,上面例子的代码中,传给Promise.resolve的那个新的promise被当作value返回了,所以这里我们要添加点代码处理下:

 1 class Promise {
2 constructor(excutor) {
3 // ...
4 const resolve = (value) => {
5 if (value instanceof Promise) {
6 return value.then(resolve, reject);
7 }
8 if (this.status === PENDING) {
9 this.value = value;
10 this.status = FULFILLED;
11 this.onResolvedCallbacks.forEach((cb) => cb(this.value));
12 }
13 };
14 // ...
15 }
16 // ...
17 }

  我们判断一下传入的value是不是一个Promise的实例,如果是的话,那么就再调用一下then,这样就可以把结果传递到下一层了。这样,我们打印的结果就符合ES6的实现了,注意,这个跟规范无关了噢。

Zaking resolve----

  你猜Promise.reject这个方法怎么实现?我不写了噢,你自己试试!要注意的是,resolve一个Promise会等待解析后的结果,但是reject一个Promise会直接走向失败。

  那么接下来我们来实现一个更简单的方法,Promise.catct:

1 catch(errCb) {
2 return this.then(null, errCb);
3 }

  就这么简单,其实catch方法本质上就是then中的onRejected这个回调,那么我们直接调用就好了,不传onFulfilled只传onRejected。

2、Promise.all的实现

  all方法其实很好理解,就是所有的回调都成功了,才算是成功,我们看个例子:

 1 Promise.all([
2 new Promise((resolve, reject) => {
3 resolve("1");
4 }),
5 new Promise((resolve, reject) => {
6 resolve("2");
7 }),
8 ]).then((data) => {
9 console.log(data, "all");
10 });

  就是这样,all方法会接收一个都是Promise的数组,然后内部会去处理这个数组,我们就来看看是怎么处理的吧:

 1 static all(promises) {
2 let result = [];
3 let times = 0;
4 return new Promise((resolve, reject) => {
5 function processResult(data, index) {
6 result[index] = data;
7 if (++times === promises.length) {
8 resolve(result);
9 }
10 }
11 for (let i = 0; i < promises.length; i++) {
12 const promise = promises[i];
13 Promise.resolve(promise).then((data) => {
14 processResult(data, i);
15 }, reject);
16 }
17 });
18 }

  其实你看代码并不多的,我们来分析下吧。首先我们声明了两个变量,一个存储结果的数组,这个结果是指resolve成功后的onFulfilled回调的value,一个是计数器。然后我们去循环整个传入的promises,让每一个promise传递给Promise.resolve去执行,然后我们用一个processResult去处理返回的data和当前的下标i,然后我们还要处理一下reject,那么这里要注意哈,对于all方法来说,只要有一个出错了,那么整个all执行的结果就是rejected的,所以reject不用特殊处理,直接reject就好了,不需要添加进数组这个那个的。

  那么继续,processResult每当我们执行一次的时候,就会根据传入的下标的位置去存储结果,这样处理其实就是为了按照传入promise的先后顺序去存储结果,然后先++times再去和promises的length长度去做比较,换句话说就是计数嘛,如果相等,就直接resolve最终的result即可。并不是很复杂噢。

3、Promise.race的实现

  这个race是什么意思呢,我们先看个例子:

const p = Promise.race([p1, p2, p3]);

  上面代码中,只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。换句话说就是,只要有一个结果就行了,不管这个结果是啥。谁跑得快我就算谁是第一,这就是race的意思。那既然是谁快我选谁,那我们代码要怎么写?循环一下全部执行一遍呗,谁有结果了就完事了呗:

1 static race(promises) {
2 return new Promise((resolve, reject) => {
3 for (let i = 0; i < promises.length; i++) {
4 let promise = promises[i];
5 Promise.resolve(promise).then(resolve, reject);
6 }
7 });
8 }

  嗯……就这么简单,其实就是all的那部分删除了一些~~

4、Promise.allSettled的实现

  这个方法是啥意思呢,就是不管成功或失败,会返回所有异步的结果。诶?那不是跟all方法很类似,只要修改一下all方法不就可以了,没错,你可真是个小聪明:

 1 static allSettled(promises) {
2 let result = [];
3 let times = 0;
4 return new Promise((resolve, reject) => {
5 function processResult(data, index, status) {
6 result[index] = { status, value: data };
7 if (++times === promises.length) {
8 resolve(result);
9 }
10 }
11 for (let i = 0; i < promises.length; i++) {
12 const promise = promises[i];
13 Promise.resolve(promise).then(
14 (data) => {
15 processResult(data, i, "fulfilled");
16 },
17 (err) => {
18 processResult(err, i, "rejected");
19 }
20 );
21 }
22 });
23 }

  你看跟all方法有啥区别?无非就是额外处理了onRejected,直接onRejected就直接失败了,现在回去走processResult方法也存到result中,当然,result存的内容也不太一样,多了个状态,会告诉你是失败的结果还是成功的结果。不复杂吧,其实就是all方法。

5、Promise.finally的实现

  finally方法用于指定不管Promise对象最后的状态如何,都会执行的操作,就是我不管你Promise最后是fulfilled还是rejected,都会执行finally的回调。那我们来看是咋实现的吧:

 1 finally(finallyCallback) {
2 let p = this.constructor;
3 return this.then(
4 (data) => {
5 return p.resolve(finallyCallback()).then(() => data);
6 },
7 (err) => {
8 return p.resolve(finallyCallback()).then(() => {
9 throw err;
10 });
11 }
12 );
13 }

  其实代码不难,但是这里面还是有点东西的。为什么我在this.then中又调用了p.resolve并且传了finally的finallyCallback回调?我直接这样写不行么?

 1 finally(finallyCallback) {
2 return this.then(
3 (data) => {
4 finallyCallback()
5 return data;
6 },
7 (err) => {
8 finallyCallback()
9 throw err;
10 }
11 );
12 }

  调用finallyCallback,返回结果。好像挺完美的。但是,不太行,因为按照上面的finally实现,你这样去写Promise的用法:

 1 Promise.resolve("zaking-finally")
2 .finally(() => {
3 console.log("finally~~~");
4 })
5 .then((data) => {
6 console.log(data, "finally-resolved");
7 })
8 .catch((err) => {
9 console.log(err, "finally-rejected");
10 });

  或者这样:

 1 Promise.resolve("zaking-finally")
2 .finally(() => {
3 console.log("finally~~~");
4 return 1;
5 })
6 .then((data) => {
7 console.log(data, "finally-resolved");
8 })
9 .catch((err) => {
10 console.log(err, "finally-rejected");
11 });

  都没问题,但是因为finallyCallback有可能是异步的,所以我们需要额外的包裹一层,再去执行最终的onFulfilled或者onRejected。比如这样:

 1 Promise.resolve("zaking-finally")
2 .finally(() => {
3 return new Promise((resolve, reject) => {
4 setTimeout(() => {
5 resolve();
6 console.log("finally");
7 }, 1000);
8 });
9 })
10 .then((data) => {
11 console.log(data, "finally-resolved");
12 })
13 .catch((err) => {
14 console.log(err, "finally-rejected");
15 });

  那么本章所有的内容就都完事了,当然我们并没有实现所有的方法,如果你真的理解了,完全可以自己去实现了,比如any方法和try方法,甚至于你可以自己去创造某些方法,因为Promise/A+规范范围内的所有内容,其实就那么点,其他的都是实现罢了,你看我讲了五个方法,实际上只讲了all和finally这两个方法。

6、promisify

  最后我们来聊一聊promisify,也是这篇文章最后的一点内容,promisify其实是Node.js提供的可以把普通带回调的函数,转换成promise对象的工具方法。我们先来看个例子,怎么用promisify:

 1 function testA(a, b, cb) {
2 cb(null, a + b);
3 }
4
5 let A = promisify(testA);
6 A(1, 2).then(
7 (data) => {
8 console.log(data, "data-----");
9 },
10 (err) => {
11 console.log(err);
12 }
13 );

  上面的代码,首先testA是一个带有回调函数作为参数的方法,当然这是一个同步回调,然后要注意的是,cb在testA中必须是最后一个参数,而执行cb的时候,第一个参数必须是错误处理的回调函数,这里我们直接用null代替了,然后,我们通过promisify方法,包裹了一下testA,生成了一个A方法,那么此时的A就是一个Promise对象了噢,然后调用Promise的then方法,传入onFulfilled和onRejected两个回调,其实这里的onFulfilled就可以理解成是cb,而onRejected就是那个null。那么我们来看下实现,代码很少:

 1 function promisify(fn) {
2 return function (...args) {
3 return new Promise((resolve, reject) => {
4 fn(...args, function (err, data) {
5 if (err) return reject(err);
6 resolve(data);
7 });
8 });
9 };
10 }

  好嘞,那么我们依照实现和例子代码,我们来分析下这个promisify是如何运行的。首先,promisify返回了一个函数,那么这个返回的函数,就是我们例子中的A方法,A传入的参数1,2也同样在promisify返回的function中通过rest参数来处理的,从而获取到我们传入的参数。然后,我们在返回的函数内部又返回了一个Promise。我们先不管这个Promise,咱们删除点东西再看一下:

1 function promisify(fn) {
2 return function (...args) {
3 return new Promise((resolve, reject) => {
4 fn();
5 });
6 };
7 }

  我们看,其实就是在返回的Promise内部执行了一下我们传入的testA方法,只不过他回调前面的参数都用rest参数的方式获取到了,那么我们testA的第三个参数就是这里的:

1 function (err, data) {
2 if (err) return reject(err);
3 resolve(data);
4 }

  这一部分,就是testA内部执行的cb了呗。err就是我们传的null,data就是那个a + b。简单吧?其实一点都不复杂。

  那你可能会问为啥要这样固定的去写testA方法呢,回调必须是最后一个参数,错误的回调还必须是回调函数的第一个参数,嗯。。是因为node的实现是这样的~~

  最后,再补充一点,这个东西理解了promisify后就很简单了,不多说了,也就是promisifyAll方法,接收一个对象作为参数:

1 function promisifyAll(obj) {
2 let result = {};
3 for (let key in obj) {
4 result[key] =
5 typeof obj[key] === "function" ? promisify(obj[key]) : obj[key];
6 }
7 return result;
8 }

  很简单,就是生成一个新的对象result,如果传入的obj中的某个key是函数,就走一下promisify方法转换,最终返回result结果对象。

  附:

  最后:由于本人能力有限,感觉写的内容并未完全覆盖使用场景,大家见谅,但是还是有参考价值的~额~~然后我第一次尝试加行号,结果博客园的行号会一起复制下来,不太友好,所以特别建议大家手打代码,嘿嘿。

Javascript之我也来手写一下Promise的更多相关文章

  1. JavaScript数组方法总结及手写

    目录 手写数组衍生方法 1.检测是否为数组 2.类数组转化为数组 3.数组扁平化 4.数组去重 5.数组使用Math.max 手写数组内置方法 1. Array.prototype.filter 2. ...

  2. 【原】手写一个promise

    上一篇文章中,我们介绍了Promise的基本使用,在这篇文章中,我们试着自己来写一个Promise,主要是学习Promise的内部机制,学习它的编程思想. !!!备注:本文写的不好,仅供自己学习之用, ...

  3. 掘金转载-手写一个Promise

    目录 一 什么是Promise ? 二 Promises/A+ 规范 2.1 术语 2.2 基本要求 2.2.1. Promise的状态 2.2.2. Then 方法 2.3 简易版实践 2.4 进一 ...

  4. 手写一个Promise/A+,完美通过官方872个测试用例

    前段时间我用两篇文章深入讲解了异步的概念和Event Loop的底层原理,然后还讲了一种自己实现异步的发布订阅模式: setTimeout和setImmediate到底谁先执行,本文让你彻底理解Eve ...

  5. [JavaScript] 的异步编程之手写一个Gernerator的例子

    <html> <head> <meta charset="UTF-8"> <title>Generator Demo</tit ...

  6. 手写基于Promise A+规范的Promise

    const PENDING = 'pending';//初始态const FULFILLED = 'fulfilled';//初始态const REJECTED = 'rejected';//初始态f ...

  7. 面试----你可以手写一个promise吗

    参考:https://www.jianshu.com/p/473cd754311f <!DOCTYPE html> <html> <head> <meta c ...

  8. 手写一个promise

    Promise A+ 规范:https://promisesaplus.com/ 注:以下代码没有通过 promises-aplus-tests 的全部测试,但基本功能还是全的( 测试结果: 864 ...

  9. 手写符合Promise/A+规范的Promise

    const PENDING = "pending"; const RESOLVED = "resolved"; const REJECTED = "r ...

随机推荐

  1. 1.9 初学者应选择哪个Linux发行版?

    前面章节中,已经对几个常见的 Linux 发行版做了简单的介绍,那么对于初学者来说,选择哪个发行版的性价比更高呢? 通常情况下,初学者学习 Linux,是为了找一份和 Linux 相关的工作,那么问题 ...

  2. Mqtt入门:在线调试连接阿里云

    近期课设需要做个东西,我想要做个上位机更好的显示. 但是一开始学习一样东西,听没头绪的,不知道从哪里入手,尝试过去B站找视频看,但是感觉视频讲的都是基础的东西,不是说人家up主讲的不好,只是对于入门, ...

  3. Vue中mixins、extends、extend和components的作用和区别

    关于mixins:官方文档: https://cn.vuejs.org/v2/guide/mixins.html 一.components Vue.component是用来注册或获取全局组件的方法,其 ...

  4. Mathtype无限试用

    PS:本文方法参考网上搜集的内容,仅做记录. 首先,默认大家都已安装Mathtype软件.如果没装的话,安装下就行.建议安装Mathtype国际版软件,因为国产mathtype会延长失败.如果失败的话 ...

  5. 204. Count Primes - LeetCode

    Queston 204. Count Primes Solution 题目大意:给一个数,求小于这个数的素数的个数 思路:初始化一个boolean数组,初始设置为true,先遍历将2的倍数设置为fal ...

  6. MySQL体系结构与数据类型

    layout: post title: "MySQL体系结构与数据类型" date: 2018-02-26 categories: MySQL tags: MySQL 一.MySQ ...

  7. linux篇-xshell连接突然报Connection closed by foreign host.

    1问题描述报错 Connection closed by foreign host. Disconnected from remote host(yaoGS) at 155513. 2登入虚拟机 在l ...

  8. 107_Power Pivot员工效率监控

    博客:www.jiaopengzi.com 焦棚子的文章目录 请点击下载附件 1.背景 在劳动密集型行业中,员工效率是一个永恒的话题. 今天把零时用工的效率提升展示及效率监控建一个PP模型并输出. 达 ...

  9. map计算

    map理解 参考1: https://github.com/rafaelpadilla/Object-Detection-Metrics 参考2:https://github.com/rafaelpa ...

  10. linux挂载新硬盘并进行分区格式化

    最近要给小伙伴们写几篇文章,关于<linux下误删除文件之后该如何恢复>.对于没有进程占用的文件想要进行数据恢复,不同的文件系统格式需要使用不同的工具,比如:ext4.xfs等.我找遍了我 ...