ES6入门八:Promise异步编程与模拟实现源码

2023-10-27

  • Promise的基本使用入门:
  • ——实例化promise对象与注册回调
  • ——宏任务与微任务的执行顺序
  • ——then方法的链式调用与抛出错误(throw new Error)
  • ——链式调用的返回值与传值
  • Promise的基本使用进阶:
  • ——then、catch、finally的使用
  • ——all、race的使用
  • Promise的实现目的
  • ——链式调用解决回调地狱
  • ——异步回调现在与未来任务分离
  • ——信任问题(控制反转):调用过早、调用过晚(不被调用)、调用次数过少过多、未能传递环境和参数、吞掉出现的错误和异常
  • Promise的实现原理与模拟实现源码

 一、Promise的基本使用入门

1.Promise是什么?

Promise是用来实现JS异步管理的解决方案,通过实例化Promise对象来管理具体的JS异步任务。

从Promise管理回调状态的角度来看,Promise又通常被称为状态机,在Promise拥有三种状态:pending、fulfilled、rejected。

用Promise解决JS异步执行的逻辑来理解,可以说Promise是一个未来的事件,也就是说Promise管理的任务并不是在JS的同步线程上立即执行,而是会等待同步线程的程序执行完以后才会执行。

 2.创建Promise实例管理一个异步事件,通过then添加(注册)异步任务:

 1 let oP = new Promise((resolve, reject) => {
 2     setTimeout(() => {
 3         //使用定时器开启异步任务,使用随机数模拟异步任务受理或者拒绝
 4         // (数字大于60表示异步任务成功触发受理,反之失败拒绝)
 5         Math.random() * 100 > 60 ? resolve("ok") : reject("no");
 6     },1000);
 7 });
 8 oP.then((val) => {
 9     console.log("受理:" + val);
10 },(reason) => {
11     console.log("拒绝:" + reason);
12 });

通过示例可以看到Promise实例化对象需要传入一个excutor函数作为参数,这个函数有两个形参resolve、reject分别表示受理事件与拒绝事件。这两个事件也就是通过实例对象调用then方法传入的两个函数,也就是说then方法传入的两个函数分别表示resolve与reject。

3.微任务与宏任务:

在js线程中,XMLHttpRequest网络请求响应事件、浏览器事件、定时器都是宏任务,会被统一放到一个任务队列中(用task queue1表示)。

而由Promise产生的异步任务resolve、reject被称为微任务(用task queue2表示)。

这两种任务的区别就是当异步任务队列中即有宏任务又有微任务时,无论宏任务比微任务早多久添加到任务队列中,都是微任务先执行,宏任务后执行。

来看下面这个示例,了解Promose微任务与宏任务的执行顺序:

 1 setTimeout(function(){
 2     console.log(0); //这是个宏任务,被先添加到异步任务队列中
 3 },0);
 4 let oP = new Promise((resolve, reject) => {
 5     resolve(1);//这是个微任务,被后添加到异步任务队列中
 6     console.log(2);//这是第一个同步任务,最先被打印到控制台
 7 });
 8 oP.then((val) => {
 9     console.log(val);
10 },null);
11 console.log(3);//这也是个同步任务,第二个被打印到控制台 

测试的打印结果必然是:2 3 1 0;这就是微任务与宏任务的区别,从这一点可以了解到,JS自身实现的Promise是一个全新的功能并非语法糖,所以除原生promise以外的promise实现都是一种模拟实现,在模拟实现中基本上都是使用setTimeout来实现Promise异步任务的,所以如果不支持原生Promise浏览器使用的是兼容的Promise插件,其Promise异步任务是宏任务,在程序执行时可能会出现与新版本浏览器原生的Promise实现的功能会有些差别,这是需要注意的一个小问题。

 4.Promise中的then的链式调用与抛出错误:

 1 let oP = new Promise((resolve, reject) => {
 2     setTimeout(() => {
 3         //使用定时器开启异步任务,使用随机数模拟异步任务受理或者拒绝
 4         // (数字大于60表示异步任务成功触发受理,反之失败拒绝)
 5         Math.random() * 100 > 60 ? resolve("ok") : reject("no");
 6     },1000);
 7 });
 8 oP.then((val) => {
 9     console.log("受理:" + val);
10 },(reason) => {
11     console.log("拒绝:" + reason);
12 }).then((val) => {
13     console.log("then2 受理:" + val);
14 },(reason) => {
15     console.log("then2 拒绝:" + reason);
16 });

在上面的示例中,第一个then肯定出现两种情况,受理或者拒绝,这个毫无疑问,但是上面的链式调用代码中第二个then注册的resolve与reject永远都只会触发受理,所以最后的执行结果是:

//第一种情况:
受理:ok
then2 受理:undefined

//第二种情况:
拒绝:no
then2 受理:undefined

ES6中的Promise实现的then的链式调用与jQuery的Deferred.then的链式调用是有区别的,jQuery中实现的链式调用的第一个then的受理或者拒绝回调被调用后,后面的then会相应的执行受理或者拒绝。但是ES6中的Promise除第一个then以外后面都是调用受理,这里不过多的讨论jQuery的Deferred的实现,但是这是一个需要注意的问题,毕竟ES6的Promise是总结了前人的经验的基础上设计的新功能,在使用与之前的相似的功能时容易出现惯性思维。

ES6中的Promise.then链式调用的正确姿势——抛出错误:

这种设计的思考逻辑是:Promise1管理异步任务___>受理                Promise1没有抛出错误:Promise2受理 

                          ___>Promise2管理Promise1___>

                       ___>拒绝                Promise2抛出错误:Promise2拒绝

所以前面的示例代码在拒绝中应该添加抛出错误才是正确的姿势:

 1 let oP = new Promise((resolve, reject) => {
 2     setTimeout(() => {
 3         Math.random() * 100 > 60 ? resolve("ok") : reject("no");
 4     },1000);
 5 });
 6 oP.then((val) => {
 7     console.log("受理:" + val);
 8 },(reason) => {
 9     console.log("拒绝:" + reason);
10     throw new Error("错误提示...");
11 }).then((val) => {
12     console.log("then2 受理:" + val);
13 },(reason) => {
14     console.log("then2 拒绝:" + reason);
15 });

在第一个then注册的reject中抛出错误,上面的示例的执行结果就会是这样了:

//第一种情况:
受理:ok
then2 受理:undefined

//第二种情况:
拒绝:no
then2 拒绝:Error: 错误提示...

好像这样的结果并不能说明之前的设计思考逻辑,仅仅只能说明then的链式调用在reject中抛出错误才能触发后面的reject,但是在我们的开发中必然会有即便异步正确受理,但不代表受理回调就能正确的执行完,受理的代码也可能会出现错误,所以在第一个then中受理回调也抛出错误的话同样会触发后面链式注册的reject,看示例:

 1 let oP = new Promise((resolve, reject) => {
 2     setTimeout(() => {
 3         //使用定时器开启异步任务,使用随机数模拟异步任务受理或者拒绝
 4         // (数字大于60表示异步任务成功触发受理,反之失败拒绝)
 5         Math.random() * 100 > 60 ? resolve("ok") : reject("no");
 6     },1000);
 7 });
 8 oP.then((val) => {
 9     console.log("受理:" + val);
10     if(Math.random() * 100 > 30){
11         return "then1受理成功执行完毕!";
12     }else{
13         throw new Error("错误提示:then1受理没有成功执行完成。");
14     }
15 },(reason) => {
16     console.log("拒绝:" + reason);
17     throw new Error("错误提示...");
18 }).then((val) => {
19     console.log("then2 受理:" + val);
20 },(reason) => {
21     console.log("then2 拒绝:" + reason);
22 });

这时候整个示例最后的执行结果就会出现三种情况:

//情况1:
受理:ok
then2 受理:then1受理成功执行完毕!

//情况2:
promise.html:24 拒绝:no
then2 拒绝:Error: 错误提示...

//情况3:
受理:ok
then2 拒绝:Error: 错误提示:then1受理没有成功执行完成。

5.then方法链式调用的返回值与传值:

在前面的代码中,相信你已经发现,如果前面then注册的回调不返回值或者不抛出错误,后面的then接收不到任何值,打印出来的参数为undefined。这一点也与jQuery中的then有些区别,在jQuery中如果前面的then没有返回值,后面then注册的回调函数会继续使用前面回调函数接收的参数。

在前面的示例中已经有返回值、抛出错误和传值的展示了,这里重点来看看如果返回值是一个Promise对象,会是什么结果:

 1 let oP = new Promise((resolve, reject) => {
 2     setTimeout(() => {
 3         //使用定时器开启异步任务,使用随机数模拟异步任务受理或者拒绝
 4         // (数字大于60表示异步任务成功触发受理,反之失败拒绝)
 5          Math.random() * 100 > 60 ? resolve("ok") : reject("no");
 6     },1000);
 7 });
 8 oP.then((val) => {
 9     console.log("受理:" + val);
10     return new Promise((resolve, reject) => {
11         Math.random() * 100 > 60 ? resolve("then1_resolve_ok") : reject("then_resolve_no");
12     });
13     
14 },(reason) => {
15     console.log("拒绝:" + reason);
16     return new Promise((resolve, reject) => {
17         Math.random() * 100 > 60 ? resolve("then1_reject_ok") : reject("then1_reject_no");
18     })
19 }).then((val) => {
20     console.log("then2 受理:" + val);
21 },(reason) => {
22     console.log("then2 拒绝:" + reason);
23 });

以上的示例出现的结果会有四种:

//情况一、二
受理:ok
then2 受理:then1_resolve_ok / then2 拒绝:then1_resolve_no

//情况三、四
拒绝:no
then2 受理:then1_reject_ok / then2 拒绝:then1_reject_no

通过示例可以看到,当前面一个then的回调返回值是一个Promise对象时,后面的then触发的受理或者拒绝是根据前面返回的Promise对象触发的受理或者拒绝来决定的。

 二、Promise的基本使用进阶

1.在Promise中标准的捕获异常的方法是catch,虽然前面的示例中使用了reject拒绝的方式捕获异常,但一般建议使用catch来实现捕获异常。需要注意的是异常一旦被捕获就不能再次捕获,意思就是如果在链式调用中前面的reject已经捕获了异常,后面链式调用catch就不能再捕获。

建议使用catch异常捕获的代码结构:

 1 let oP = new Promise((resolve, reject) => {
 2     setTimeout(() => {
 3         //使用定时器开启异步任务,使用随机数模拟异步任务受理或者拒绝
 4         // (数字大于60表示异步任务成功触发受理,反之失败拒绝)
 5          Math.random() * 100 > 60 ? resolve("ok") : reject("no");
 6     },1000);
 7 });
 8 oP.then((val) => {
 9     console.log("受理:" + val);
10 },(reason) => {
11     console.log("拒绝:" + reason);
12     throw new Error("错误提示...");
13 }).then((val) => {
14     console.log("then2 受理:" + val);
15 }).catch((err) => {
16     console.log(err);
17 })

 catch不能捕获的情况:

 1 let oP = new Promise((resolve, reject) => {
 2     setTimeout(() => {
 3         //使用定时器开启异步任务,使用随机数模拟异步任务受理或者拒绝
 4         // (数字大于60表示异步任务成功触发受理,反之失败拒绝)
 5          Math.random() * 100 > 60 ? resolve("ok") : reject("no");
 6     },1000);
 7 });
 8 oP.then((val) => {
 9     console.log("受理:" + val);
10 },(reason) => {
11     console.log("拒绝:" + reason);
12     throw new Error("错误提示...");
13 }).then((val) => {
14     console.log("then2 受理:" + val);
15 }, (reason) => {
16     console.log("then2 拒绝:" + reason);
17 }).catch((err) => {
18     console.log("异常捕获:",err);
19 })
20 //这种情况就只能是在第二个then中的reject捕获异常,catch不能捕获到异常

一个非技术性的问题,调用了一个空的then会被忽视,后面的then或者catch依然正常执行:

 1 let oP = new Promise((resolve, reject) => {
 2     setTimeout(() => {
 3         //使用定时器开启异步任务,使用随机数模拟异步任务受理或者拒绝
 4         // (数字大于60表示异步任务成功触发受理,反之失败拒绝)
 5          Math.random() * 100 > 60 ? resolve("ok") : reject("no");
 6     },1000);
 7 });
 8 oP.then((val) => {
 9     console.log("受理:" + val);
10 },(reason) => {
11     console.log("拒绝:" + reason);
12     throw new Error("错误提示...");
13 })
14 .then()//这个then会被忽视,如果前面一个then调用了reject拒绝,后面的catch能正常捕获(或者后面链式调用then都能正常执行)
15 .catch((err) => {
16     console.log("异常捕获:",err);
17 });

2.在Promise方法中,除了finally都会继续返回Promise对象,而且finally传入的回调函数一定会被执行,这个跟前面的一种情况非常类似,就是当前面的then不抛出错误的时候,后面的then一定是调用受理,实际上底层的实现也就是同一个逻辑上实现的。只是finally不再返回Promise对象,但需要注意的是finally注册的回调函数获取不到任参数。

 1 let oP = new Promise((resolve, reject) => {
 2     setTimeout(() => {
 3         //使用定时器开启异步任务,使用随机数模拟异步任务受理或者拒绝
 4         // (数字大于60表示异步任务成功触发受理,反之失败拒绝)
 5          Math.random() * 100 > 60 ? resolve("ok") : reject("no");
 6     },1000);
 7 });
 8 oP.then((val) => {
 9     console.log("受理:" + val);
10 },(reason) => {
11     console.log("拒绝:" + reason);
12     throw new Error("错误提示...");
13 }).catch((err) => {
14     console.log("异常捕获:",err);
15 }).finally(() => {
16     console.log("结束");//这个回调函数接收不到任何参数
17 })

3.Promise给并发处理提供了两种实现方式all、race,这两个的处理逻辑非常类似条件运算符的与(&&)或 (||)运算,all就是用来处理当多个Promise全部成功受理就受理自身的受理回调resolve,否则就拒绝reject。race的处理多个Promise只需要一个Promise成功受理就触发自身的受理回调,否则就拒绝reject。它们处理Promise实例的方式都是将Promise实例对象作为数组元素,然后将包裹的数组作为all或race的参数进行处理。

 

这里使用一段nodejs环境读取文件代码来展示Promise.all的使用:

//路径+文件名:            内容:data
./data/number.txt        "./data/name.txt"
./data/name.txt          "./data/score.tet"
./data/score/txt         "99"

//src目录结构
--index.js
--data
----number.txt
----name.txt
----score.txt

Promise.all实现文件数据并发读取:

 1 let fs = require('fs');
 2 
 3 function readFile(path){
 4     return new Promise((resolve,reject) => {
 5         fs.readFile(path,'utf-8', (err,data) => {
 6             if(data){
 7                 resolve(data);
 8             }else{
 9                 reject(err);
10             }
11         });
12     });
13 }
14 
15 Promise.all([readFile("./data/number.txt"),readFile("./data/name.txt"),readFile("./data/score.txt")]).then((val) =>{
16     console.log(val);
17 });

在nodejs环境中执行代码,打印结果:

node index.js  //执行js文件
[ './data/name.txt', './data/score.txt', '99' ] //打印结果

从示例中可以看到Promise.all获取的值是全部Promise实例受理回调传入的值,并且以数组的方式传入。

 

接着来看一个Promise.race的示例,这个示例:

 1 var op1 = new Promise((resolve, reject) => {
 2     setTimeout(resolve, 500, "one");
 3 });
 4 var op2 = new Promise((resolve, reject) => {
 5     setTimeout(resolve, 100, "two");
 6 });
 7 Promise.race([op1,op2]).then((val) => {
 8     console.log(val);
 9 });
10 //打印结果:two

Promise.race获的值是第一个Promise实例受理回调传入的值。

 

4.Promise.all与Promise.race的传值规则:

 all:

所有Promise实例受理resolve,即所有异步回调成功的情况下,将所有Promise实例的resolve接收的参数合并成一个数组,传递给Promise.all生成的新的Promise实例的resolve回调处理。

如果有一个失败的情况下,即Promise.all生成的新的Promise实例触发回调reject函数,这个函数会接收到最先失败的Promise实例通过reject回调传入的参数。

 

race:

通过Promise.race处理的Promise实例中最先获得结果的Promise实例的参数,传递给Promise.race产生的Promise实例,不论成功与失败,成功就出发resolve函数,失败就出发reject函数。

 三、Promise的实现目的

1.链式调用解决回调地狱:在一开始学习编程的时候我们一定都写过一连串的作用域嵌套代码,来解决一些业务逻辑链相对比较长的功能,然后还可能跟同学炫耀“你看我把这个功能写出来了,还能正确执行”。不要问我为什么这么肯定,这种事我做过,我的同学和朋友也有做过。这为什么值得炫耀呢?无非就是面对这种业务逻辑链比较长的功能很难保证在那个不环节不出错,所以能驾驭层层嵌套的代码的确可以说很“认真”的在编码。我在jQuery的ajax的一篇博客中就是用了一个非常详细的案例展示了回调地狱:jQuery使用(十二):工具方法之ajax的无忧回调(优雅的代码风格)

这里我使用第二节中的(3:Promise.all)案例,(应用之前的文件结构)来写一个文件层级读取的示例:

 1 //这是一个基于nodejs环境的js示例,请在nodejs环境中执行index.js
 2 let fs = require('fs');
 3 
 4 fs.readFile("./data/number.txt","utf-8",(err,data) => {
 5     if(data){
 6         fs.readFile(data,"utf-8",(err,data) => {
 7             if(data){
 8                 fs.readFile(data,"utf-8",(err,data) => {
 9                     console.log(data);
10                 })
11             }
12         })
13     }
14 });

相信大家遇到这种代码都会知道这样的代码结构,不易于维护,编写容易出错并且还不容易追踪错误。下面来看看使用Promise如何回避这样的问题,来提高代码质量:

 1 //这是一个基于nodejs环境的js示例,请在nodejs环境中执行index.js
 2 let fs = require('fs');
 3 
 4 function readFile(path){
 5     return new Promise((resolve,reject) => {
 6         fs.readFile(path,'utf-8', (err,data) => {
 7             if(data){
 8                 resolve(data);
 9             }else{
10                 reject(err);
11             }
12         });
13     });
14 }
15 readFile("./data/number.txt").then((val) => {
16     return readFile(val);//这里去获取nama.text的文本数据
17 },(reason) => {
18     console.log(reason);
19 }).then((val) => {
20     return readFile(val);//这里去获取score.text的文本数据
21 },(reason) => {
22     console.log(reason);
23 }).then((val) => {
24     console.log(val);//这里最后打印score.text的文本数据
25 },(reason) => {
26     console.log(reason);
27 });

2.异步回调现在与未来任务分离:

Kyle Simpson大神在《你不知道的js中卷》的第二部分第一章(1.3并行线程)中给我说明了一个我长期混洗的知识点,“异步”与“并行”,他明确的阐述了异步是关于现在和将来的事件间隙,而并非关于能同时发生的事情。

简单来说,在js中我们可以把同步任务理解为现在要执行的任务,异步则是将来要执行的任务,个人认为这是Promise的核心功能,Promise的then本质上就是这样的设计思路,在实例化的Promise对象的时候就已经调用了回调任务resolve或者reject,但是Promise将这两个回调任务处理成了异步(微任务)模式,通过前面的应用介绍我们知道Promise实例化的时候并没有添加这两个任务,而是后面基于同步任务的then添加的,所以resolve和reject才能在未来有真正的任务可以执行。

利用异步的这种现在与未来的异步设计思路实现了Promise.all和Promise.race,解决了前端回调的竞态问题。关于js竞态问题可以了解《你不知道的js中卷》第二部分第一章和第三章的3.1。(这给内容可多可少,但是想想Kyle Simpson的清晰明了的分析思路,建议大家去看他书。)

3.信任问题(控制反转):

相信大家在应用js开发的时候都使用果类似这样的代码:

ajax("...",function(...){ ... })

通常这样的代码我们都会想到插件或者第三方库,如果这是一个购物订单,你知道这段代码存在多大的风险吗?我们根本就不知道这个回调函数会被执行多少次,因为怎么执行是由别让人的插件和库来控制的。顺着这个思路,在《你不知道的js中卷》的第二部分第二章2.3.1最后,大神提出这样的追问:调用过早怎么办?调用过晚怎么办?调用多次或者次数太少怎么办?没有传递参数或者环境怎么办?出现错误或者异常怎么办?这些内容在《你不知道的js中卷》第二部分第三章3.3都详细的描述了基于Promise的解决方案。

本质上也就是Promise的控制反转的设计模式,比如前面的ajax()请求可以这样来写:

var oP = new Promise((resolve,reject) => {   
      resolve(...);
});
oP.then((val) => {
    ajax("...",function(...){...});
});

我们知道,每个Promise只能决议一次,无论成功或者失败,所以就不用当心一个购物订单请求会不会被插件或者第三方库误操作发送多次(这并不是绝对的,毕竟ajax回调函数内部怎么执行还是别人的代码,这里我能只能假设ajax回调函数是可信任的)。

关于Promise的实现目的还有很多,我也只能在这里列举一些比较典型的和常见的问题,如果想了解更多我首先建议大家去看我前面多次提到的书,或者到各大技术论坛了解他人的研究和发现,下面接着进入激动人心的Promise源码部分。

 四、Promise的实现原理与模拟实现源码

Promise实现标准文档:https://promisesaplus.com

由于源码的复杂性还算比较高,我们采用分阶段实现的方式,从Promise的一部分功能开始然后逐渐完成所有功能。

第一阶段:基于Promise的三种状态:pending、Fulfilled、Rejected实现同步的状态决议回调任务处理;

第二阶段:基于阶段一的状态机实现Promise异步的状态决议回调任务处理;

第三阶段:实现then的链式调用;

第四阶段:使用setTimeout模拟实现Promise异步回调任务、处理回调任务中的异常、忽略链式调用中的空then;

第五阶段:实现回调函数返回Promise实例;

第六阶段:实现Promise静态方法race、all;

第七阶段:实现Promise原型方法catch、finally、以及扩展一个deferred静态方法

1.原理分析之状态:

Promise实例三种状态:pending、Fulfilled、Rejected,当pending状态时表示未决议,可转换成Fulfilled或者Rejected状态,转换状态后不可更改。

Promise实例化时执行excutor函数,并使用try...catch处理excutor可能抛出的错误行为,如果抛出错误,将状态设置为Rejected(拒绝)。

在原型上定义then方法,实现回调任务处理。

 1 function myPromise(excutor){
 2     var self = this;
 3     self.status = "pending";
 4     self.resolveValue = null; //缓存受理回调的参数
 5     self.rejectReason = null; //缓存拒绝回调的参数
 6     function resolve(value){
 7         if(self.status === "pending"){
 8             self.status = "Fulfilled";
 9             self.resolveValue = value; // 将受理回调执行的参数缓存到Promise实例属性上
10         }
11     }
12 
13     function reject(reason){
14         if(self.status === "pending"){
15             self.status = "Rejected";
16             self.rejectReason = reason; // 将拒绝回调执行的参数缓存到Promise实例属性上
17         }
18     }
19     //当excutor抛出错误执行reject
20     try{
21         excutor(resolve,reject);
22     }catch(e){
23         reject(e);
24     }
25     
26 };
27 
28 myPromise.prototype.then = function(onFulfilled,onRejected){
29     var self = this;
30     if(self.status === "Fulfilled"){
31         onFulfilled(self.resolveValue);
32     }
33     if(self.status === "Rejected"){
34         onRejected(self.rejectReason);
35     }
36 }

测试代码:

 1 var myP = new myPromise((resolve,reject) => {
 2     // 测试resolve
 3     // resolve("受理");
 4     // 测试reject
 5     // reject("拒绝");
 6     // 测试抛出错误
 7     throw new Error("excutor抛出错误");
 8 });
 9 myP.then((val) => {
10     console.log(val);
11 }, (reason) => {
12     console.log(reason);
13 });

2.Promise原理分析之异步:

这部分还不是解析Promise微任务的内容,而是解析当excutor内决议是一个异步任务,比如ajax请求的回调任务,这种情况就是then的注册行为会在状态变化之前,所以需要将注册回调函数缓存下来,等到异步任务执行时调用。

 1 function myPromise(excutor){
 2     var self = this;
 3     self.status = "pending";
 4     self.resolveValue = null; //缓存受理回调的参数
 5     self.rejectReason = null; //缓存拒绝回调的参数
 6     self.ResolveCallBackList = []; //当Promise是一个异步任务时,缓存受理回调函数
 7     self.RejectCallBackList = [];  //当Promise是一个异步任务时,缓存拒绝回调函数
 8 
 9     function resolve(value){
10         if(self.status === "pending"){
11             self.status = "Fulfilled";
12             self.resolveValue = value; // 将受理回调执行的参数缓存到Promise实例属性上
13             self.ResolveCallBackList.forEach(function(ele){ 
14                 //这里当excutor内是同步任务时,ResolveCallBackList没有元素,当excutor内是一个异步任务时就会执行then缓存的受理回调函数
15                 ele();
16             });
17         }
18     }
19 
20     function reject(reason){
21         if(self.status === "pending"){
22             self.status = "Rejected";
23             self.rejectReason = reason; // 将拒绝回调执行的参数缓存到Promise实例属性上
24             self.RejectCallBackList.forEach(function(ele){
25                 //这里当excutor内是同步任务时,RejectCallBackList没有元素,当excutor内是一个异步任务时就会执行then缓存的拒绝回调函数
26                 ele();
27             });
28         }
29     }
30     //当excutor抛出错误执行reject
31     try{
32         excutor(resolve,reject);
33     }catch(e){
34         reject(e);
35     }
36     
37 };
38 
39 myPromise.prototype.then = function(onFulfilled,onRejected){
40     var self = this;
41     if(self.status === "Fulfilled"){
42         onFulfilled(self.resolveValue);
43     }
44     if(self.status === "Rejected"){
45         onRejected(self.rejectReason);
46     }
47     // 当excutor执行时,内部回调是一个异步任务,Promise的状态不会发生改变
48     // 所以异步作为一个将来任务,先缓存到Promise实例对象上
49     if(self.status === "pending"){
50         self.ResolveCallBackList.push(function(){
51             onFulfilled(self.resolveValue);
52         });
53 
54         self.RejectCallBackList.push(function(){
55             onRejected(self.rejectReason);
56         })
57     }
58 }

测试代码:(异步任务的出现异常报错这部分还没处理,所以只测试异步任务的受理或拒绝)

 1 var myP = new myPromise((resolve,reject) => {
 2     setTimeout(() => {
 3         // 测试resolve
 4         resolve("受理");
 5         // 测试reject
 6         // reject("拒绝");
 7     },1000);
 8 });
 9 myP.then((val) => {
10     console.log(val);
11 }, (reason) => {
12     console.log(reason);
13 });

3.then的链式调用:

在ES6的Promise中,then的链式调用是返回一个全新的Promise实例,这一点在前面的应用中已经有说明,链式调用中除了返回一个全新的Promise对象以外,还有一个关键的问题就是将前面的Promise的的返回值,作为参数传给后面一个Promise实例的回调函数使用。

这个阶段暂时不处理返回Promise实例的相关内容,所以还记得我在使用的第一节第四小点,这里测试第一个Promise实例的resolve和reject第二个then注册受理和拒绝只会触发受理。

所以这样作为一个基本链式调用实现就非常的简单了,因为Promise实例化时需要执行一个同步的回调函数excutor,我们都知道,then的回调注册时同步进行,所以我们只需要将then的注册放到需要心生成的Promise实例化时同步执行excutor中,然后获取前一个Promise的回调执行返回值,作为新生成的Promise实例回调的参数传入即可,这个说起来好像有点复杂,但是实现非常的简单,建议直接看代码:

 1 function myPromise(excutor){
 2     var self = this;
 3     self.status = "pending";
 4     self.resolveValue = null; //缓存受理回调的参数
 5     self.rejectReason = null; //缓存拒绝回调的参数
 6     self.ResolveCallBackList = []; //当Promise是一个异步任务时,缓存受理回调函数
 7     self.RejectCallBackList = [];  //当Promise是一个异步任务时,缓存拒绝回调函数
 8 
 9     function resolve(value){
10         if(self.status === "pending"){
11             self.status = "Fulfilled";
12             self.resolveValue = value; // 将受理回调执行的参数缓存到Promise实例属性上
13             self.ResolveCallBackList.forEach(function(ele){ 
14                 //这里当excutor内是同步任务时,ResolveCallBackList没有元素,当excutor内是一个异步任务时就会执行then缓存的受理回调函数
15                 ele();
16             });
17         }
18     }
19 
20     function reject(reason){
21         if(self.status === "pending"){
22             self.status = "Rejected";
23             self.rejectReason = reason; // 将拒绝回调执行的参数缓存到Promise实例属性上
24             self.RejectCallBackList.forEach(function(ele){
25                 //这里当excutor内是同步任务时,RejectCallBackList没有元素,当excutor内是一个异步任务时就会执行then缓存的拒绝回调函数
26                 ele();
27             });
28         }
29     }
30     //当excutor抛出错误执行reject
31     try{
32         excutor(resolve,reject);
33     }catch(e){
34         reject(e);
35     }
36     
37 };
38 
39 myPromise.prototype.then = function(onFulfilled,onRejected){
40     var self = this;
41 
42     var nextPromise = new myPromise(function (resolve,reject) {
43 
44         if(self.status === "Fulfilled"){
45                 var nextResolveValue = onFulfilled(self.resolveValue);
46                 resolve(nextResolveValue);//将获取的前一个Promise回调任务的返回值传给新生成的Promise实例的受理回调任务
47         }
48         if(self.status === "Rejected"){
49                 var nextRejectValue = onRejected(self.rejectReason);
50                 resolve(nextRejectValue);//将获取的前一个Promise回调任务的返回值传给新生成的Promise实例的受理回调任务
51         }
52         // 当excutor执行时,内部回调是一个异步任务,Promise的状态不会发生改变
53         // 所以异步作为一个将来任务,先缓存到Promise实例对象上
54         if(self.status === "pending"){
55             self.ResolveCallBackList.push(function(){
56                     var nextResolveValue = onFulfilled(self.resolveValue);
57                     resolve(nextResolveValue);//将获取的前一个Promise回调任务的返回值传给新生成的Promise实例的受理回调任务
58             });
59 
60             self.RejectCallBackList.push(function(){
61                     var nextRejectValue = onRejected(self.rejectReason);
62                     resolve(nextRejectValue);//将获取的前一个Promise回调任务的返回值传给新生成的Promise实例的受理回调任务
63             });
64         }
65     });
66     return nextPromise;
67 }

测试代码:

 1 var myP = new myPromise((resolve,reject) => {
 2     setTimeout(() => {
 3         // 测试resolve
 4         // resolve(0);
 5         // 测试reject
 6         reject(0);
 7     },1000);
 8 });
 9 myP.then((val) => {
10     console.log("受理:" + val);
11     return 1;
12 }, (reason) => {
13     console.log("拒绝:" + reason);
14     return "1";
15 }).then((val) => {
16     console.log("受理:" + val);
17 }, (reason) => {
18     console.log("拒绝:" + reason);//这个暂时不会执行到
19 });

4.使用setTimeout模拟实现Promise异步回调任务:

注意这种模拟实现与ES6实现的异步回调有一个根本性差异,ES6的Promise异步回调任务是微任务,但是通过setTimeout模拟实现的是宏任务。实现其实也是非常的简单,只需要将回调任务放到setTimeout的回调函数中即可,并设置延迟时间为0;

然后再在这部分实现对回调任务中抛出错误的处理,这是因为回调任务中的错误需要在下一个Promise的reject中或者catch中被捕获,所以有了链式调用的基础就可以来实现这个功能了。

还有第二节第一小点钟提到忽略链式调用中的空then(),关于这个问题我前面只在使用中说会忽略这个空then,但是实际底层的实现并非时忽略,而是将前一个Promise基于这个空的Promise实例传递给了下一个非空的Promise。这里我们先来看一段基于原生的Promise手动传递应用:

var myP = new Promise((resolve,reject) => {
    setTimeout(() => {
        // 测试resolve
        // resolve(0);
        // 测试reject
        reject(0);
    },1000);
});
myP.then((val) => {
    console.log("受理:" + val);
    return 1;
}, (reason) => {
    console.log("拒绝:" + reason);
    // 测试Error
    throw new error("这里抛出错误");
}).then((val) => {
    return val; //将前一个受理的返回值传递给下一个Promise受理回调
},(reason) => {
    throw new Errror(reason); //将前一个Promise抛出的错误传递给下一个Promise的拒绝回调
})
.then((val) => {
    console.log("受理:" + val);
}, (reason) => {
    console.log("拒绝:" + reason);//这个暂时不会执行到
});

实际上,Promise底层也是基于这样的传递行为来处理空then的,而且在前面的Promise应用介绍中,有一种情况没有深入的说明,就是当then(null,(...) => {...})、then((...) => {...},null)、then(null,null)进行深入的说明,请示本质上同样是使用了上面示例中的传递行为。还是那句话,说起来非常复杂,实际代码非常简单:

  1 function myPromise(excutor){
  2     var self = this;
  3     self.status = "pending";
  4     self.resolveValue = null; //缓存受理回调的参数
  5     self.rejectReason = null; //缓存拒绝回调的参数
  6     self.ResolveCallBackList = []; //当Promise是一个异步任务时,缓存受理回调函数
  7     self.RejectCallBackList = [];  //当Promise是一个异步任务时,缓存拒绝回调函数
  8 
  9     function resolve(value){
 10         if(self.status === "pending"){
 11             self.status = "Fulfilled";
 12             self.resolveValue = value; // 将受理回调执行的参数缓存到Promise实例属性上
 13             self.ResolveCallBackList.forEach(function(ele){ 
 14                 //这里当excutor内是同步任务时,ResolveCallBackList没有元素,当excutor内是一个异步任务时就会执行then缓存的受理回调函数
 15                 ele();
 16             });
 17         }
 18     }
 19 
 20     function reject(reason){
 21         if(self.status === "pending"){
 22             self.status = "Rejected";
 23             self.rejectReason = reason; // 将拒绝回调执行的参数缓存到Promise实例属性上
 24             self.RejectCallBackList.forEach(function(ele){
 25                 //这里当excutor内是同步任务时,RejectCallBackList没有元素,当excutor内是一个异步任务时就会执行then缓存的拒绝回调函数
 26                 ele();
 27             });
 28         }
 29     }
 30     //当excutor抛出错误执行reject
 31     try{
 32         excutor(resolve,reject);
 33     }catch(e){
 34         reject(e);
 35     }
 36     
 37 };
 38 
 39 myPromise.prototype.then = function(onFulfilled,onRejected){
 40     if(!onFulfilled){ //当没有传入受理回调函数时,自动将参数传递给下一个Promise实例的受理函数作为参数
 41         onFulfilled = function(val){
 42             return val;
 43         }
 44     }
 45     if(!onRejected){ //当没有传入拒绝回调函数时,自动将参数传递给下一个Promise实例的拒绝函数作为参数
 46         // 可能这里你会疑惑,为什么要使用抛出错误的方式传递
 47         // 前面已经说明过,拒绝回调只有在Promise实例化中调用了拒绝回调函数以外,只有抛出错误才会会触发下一个Promise实例的拒绝回调
 48         onRejected = function(reason){
 49             throw new Error(reason);
 50         }
 51     }
 52     var self = this;
 53 
 54     var nextPromise = new myPromise(function (resolve,reject) {
 55 
 56         if(self.status === "Fulfilled"){
 57             setTimeout(function(){    //使用setTimeout模拟实现异步回调
 58                 try{                //使用try...catch来捕获回调任务的异常
 59                     var nextResolveValue = onFulfilled(self.resolveValue);
 60                     resolve(nextResolveValue);//将获取的前一个Promise回调任务的返回值传给新生成的Promise实例的受理回调任务
 61                 }catch(e){
 62                     reject(e);
 63                 }
 64                 
 65             },0);
 66         }
 67         if(self.status === "Rejected"){
 68             setTimeout(function(){
 69                 try{
 70                     var nextRejectValue = onRejected(self.rejectReason);
 71                     resolve(nextRejectValue);//将获取的前一个Promise回调任务的返回值传给新生成的Promise实例的受理回调任务
 72                 }catch(e){
 73                     reject(e);
 74                 }
 75                 
 76             },0);
 77         }
 78         // 当excutor执行时,内部回调是一个异步任务,Promise的状态不会发生改变
 79         // 所以异步作为一个将来任务,先缓存到Promise实例对象上
 80         if(self.status === "pending"){
 81             self.ResolveCallBackList.push(function(){
 82                 setTimeout(function(){
 83                     try{
 84                         var nextResolveValue = onFulfilled(self.resolveValue);
 85                         resolve(nextResolveValue);//将获取的前一个Promise回调任务的返回值传给新生成的Promise实例的受理回调任务
 86                     }catch(e){
 87                         reject(e);
 88                     }
 89                     
 90                 },0);
 91             });
 92 
 93             self.RejectCallBackList.push(function(){
 94                 setTimeout(function(){
 95                     try{
 96                         var nextRejectValue = onRejected(self.rejectReason);
 97                         resolve(nextRejectValue);//将获取的前一个Promise回调任务的返回值传给新生成的Promise实例的受理回调任务
 98                     }catch(e){
 99                         reject(e);
100                     }
101                     
102                 },0);
103             });
104         }
105     });
106     return nextPromise;
107 }

这部分功能已经非常接近原生Promise了,就不提供测试代码了,下面直接进入第五阶段。

5.实现回调函数返回Promise实例:

关于回调返回Promise实例与前面的空then的处理思路非常相识,在空then的情况下我们需要将值传递给下一个then生成的Promise实例。那回调返回Promise实例就是需要,将原本注册给then自身生成的Promise实例的回调重新注册给上一个Promise回调返回的Promise实例,实现代码同样非常简单:

  1 function myPromise(excutor){
  2     var self = this;
  3     self.status = "pending";
  4     self.resolveValue = null; //缓存受理回调的参数
  5     self.rejectReason = null; //缓存拒绝回调的参数
  6     self.ResolveCallBackList = []; //当Promise是一个异步任务时,缓存受理回调函数
  7     self.RejectCallBackList = [];  //当Promise是一个异步任务时,缓存拒绝回调函数
  8 
  9     function resolve(value){
 10         if(self.status === "pending"){
 11             self.status = "Fulfilled";
 12             self.resolveValue = value; // 将受理回调执行的参数缓存到Promise实例属性上
 13             self.ResolveCallBackList.forEach(function(ele){ 
 14                 //这里当excutor内是同步任务时,ResolveCallBackList没有元素,当excutor内是一个异步任务时就会执行then缓存的受理回调函数
 15                 ele();
 16             });
 17         }
 18     }
 19 
 20     function reject(reason){
 21         if(self.status === "pending"){
 22             self.status = "Rejected";
 23             self.rejectReason = reason; // 将拒绝回调执行的参数缓存到Promise实例属性上
 24             self.RejectCallBackList.forEach(function(ele){
 25                 //这里当excutor内是同步任务时,RejectCallBackList没有元素,当excutor内是一个异步任务时就会执行then缓存的拒绝回调函数
 26                 ele();
 27             });
 28         }
 29     }
 30     //当excutor抛出错误执行reject
 31     try{
 32         excutor(resolve,reject);
 33     }catch(e){
 34         reject(e);
 35     }
 36     
 37 };
 38 
 39 //用来处理回调返回值的情况:当返回值为Promise时,将回调函数注册到该Promise上,如果为普通值直接执行回调函数
 40 function ResolutionRetrunPromise(returnValue,res,rej){
 41     if(returnValue instanceof myPromise){
 42         returnValue.then(function(val){
 43             res(val);
 44         },function(reason){
 45             rej(reason);
 46         });
 47     }else{
 48         res(returnValue);
 49     }
 50 }
 51 
 52 
 53 myPromise.prototype.then = function(onFulfilled,onRejected){
 54     if(!onFulfilled){ //当没有传入受理回调函数时,自动将参数传递给下一个Promise实例的受理函数作为参数
 55         onFulfilled = function(val){
 56             return val;
 57         }
 58     }
 59     if(!onRejected){ //当没有传入拒绝回调函数时,自动将参数传递给下一个Promise实例的拒绝函数作为参数
 60         // 可能这里你会疑惑,为什么要使用抛出错误的方式传递
 61         // 前面已经说明过,拒绝回调只有在Promise实例化中调用了拒绝回调函数以外,只有抛出错误才会会触发下一个Promise实例的拒绝回调
 62         onRejected = function(reason){
 63             throw new Error(reason);
 64         }
 65     }
 66     var self = this;
 67 
 68     var nextPromise = new myPromise(function (resolve,reject) {
 69 
 70         if(self.status === "Fulfilled"){
 71             setTimeout(function(){    //使用setTimeout模拟实现异步回调
 72                 try{                //使用try...catch来捕获回调任务的异常
 73                     var nextResolveValue = onFulfilled(self.resolveValue);
 74                     ResolutionRetrunPromise(nextResolveValue,resolve,reject);//使用回调返回值来处理下一个回调任务
 75                 }catch(e){
 76                     reject(e);
 77                 }
 78                 
 79             },0);
 80         }
 81         if(self.status === "Rejected"){
 82             setTimeout(function(){
 83                 try{
 84                     var nextRejectValue = onRejected(self.rejectReason);
 85                     ResolutionRetrunPromise(nextRejectValue,resolve,reject);
 86                 }catch(e){
 87                     reject(e);
 88                 }
 89                 
 90             },0);
 91         }
 92         // 当excutor执行时,内部回调是一个异步任务,Promise的状态不会发生改变
 93         // 所以异步作为一个将来任务,先缓存到Promise实例对象上
 94         if(self.status === "pending"){
 95             self.ResolveCallBackList.push(function(){
 96                 setTimeout(function(){
 97                     try{
 98                         var nextResolveValue = onFulfilled(self.resolveValue);
 99                         ResolutionRetrunPromise(nextResolveValue,resolve,reject);
100                     }catch(e){
101                         reject(e);
102                     }
103                     
104                 },0);
105             });
106 
107             self.RejectCallBackList.push(function(){
108                 setTimeout(function(){
109                     try{
110                         var nextRejectValue = onRejected(self.rejectReason);
111                         ResolutionRetrunPromise(nextRejectValue,resolve,reject);
112                     }catch(e){
113                         reject(e);
114                     }
115                     
116                 },0);
117             });
118         }
119     });
120     return nextPromise;
121 }

测试代码:

 1 var mP = new myPromise((resolve,reject) => {
 2     // resolve("受理1");
 3     reject("拒绝1");
 4 });
 5 mP.then((val) => {
 6     console.log(val);
 7     return new myPromise((resolve,reject) => {
 8         // resolve("受理2");
 9         // reject("拒绝2");
10     });
11 },(reason) => {
12     console.log(reason);
13     return new myPromise((resolve,reject) => {
14         // resolve("受理2");
15         reject("拒绝2");
16     });
17 }).then((val) => {
18     console.log(val);
19 },(reason) => {
20     console.log(reason);
21 })

6.实现Promise静态方法race、all:

 1 myPromise.race = function(promiseArr){
 2     return new myPromise(function(resolve,reject){
 3         promiseArr.forEach(function(promise,index){
 4             promise.then(resolve,reject);
 5         });
 6     });
 7 }
 8 
 9 //all通过给每个Promise传递一个受理回调,这个回调负责获取每个受理函数的参数,并判断是否全部受理,如果全部受理触发all自身的受理回调
10 //另外将all的reject传递给每个Promise的reject,只要任意一个触发就完成all的拒绝回调
11 myPromise.all = function(promiseArr){
12     function gen(length,resolve){
13         var count = 0;
14         var values = [];
15         return function(i,value){
16             values[i] = value;
17             if(++count === length){
18                 resolve(values);
19             }
20         }
21     }
22     return new myPromise(function(resolve,reject){
23         let done = gen(promiseArr.length,resolve);
24         promiseArr.forEach(function(promise,index){
25             promise.then((val) => {
26                 done(index,val);
27             },reject);
28         })
29     })
30 }

7.实现Promise原型方法catch、finally、以及扩展一个deferred静态方法

 1 //原型方法catch的实现
 2 myPromise.prototype.catch = function(onRejected){
 3     return this.then(null,onRejected);
 4 }
 5 // 原型方法finally
 6 myPromise.prototype.finally = function(fun){
 7     fun();
 8 }
 9 //扩展静态方法deferred方法
10 myPromise.deferred = function(){
11     var defer = {};
12     defer.promise = new Promise((resolve,reject) => {
13         defer.resolve = resolve;
14         defer.reject = reject;
15     });
16     return defer;
17 }

(全文完)

 

转载于:https://www.cnblogs.com/ZheOneAndOnly/p/11411948.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ES6入门八:Promise异步编程与模拟实现源码 的相关文章

  • jQuery mobile 中的文本区域高度和宽度?

    我修复了 jQuery mobile 中文本区域元素的高度 并且在纵向中得到了完美的高度和宽度 但在横向中宽度没有放大 谁能帮我 提前致谢 HTML
  • Angular UI 模式的范围问题

    我无法理解 使用角度 UI 模式的范围 虽然这里不是很明显 但我已经正确设置了模块和所有内容 据我所知 但这些代码示例尤其是我发现错误的地方 index html 其中重要部分 div class btn group div
  • 如何删除除任何特定 id 之外的元素

    假设有一个父 id 其中包含许多元素 我想删除除一个元素之外的所有元素 ex parent id children not id n remove
  • 如何阻止直接访问我的 JavaScript 文件?

    我使用 Minify 来缩小并缓存所有脚本请求 我只希望我的用户能够访问 JavaScript 文件的缩小版本 缩小位于www example com min我的脚本位于www example com scripts 如何阻止直接访问doc
  • Angular.js:如何从无序列表中获取 orderBy 或过滤器来工作?

    尝试根据价格和评级 在返回的对象中 进行排序 我宁愿用 ng click 和 li 来代替使用选择菜单 有没有办法做到这一点 我环顾四周 这是我能想到的最接近的 ul class restaurant filter li i class i
  • 想要动态处理与分页相关的页码显示:ReactJS

    我有一些分页逻辑工作得很好 唯一的问题是我只能让它显示并固定数量的页面可供选择 现在我已经把它放到了 5 页 但我希望它能够根据总记录动态更改 假设我有 100 条记录 每页限制为 10 条 将有 10 页 现在我只能让它以这种方式显示 第
  • 如何使用javascript确保元素仅在圆上朝一个方向移动?

    好吧 我承认我对三角学真的很糟糕 出于上下文的考虑 我将添加我在这里提到的问题中的内容 参考问题 https stackoverflow com a 39429290 168492 https stackoverflow com a 394
  • 如何在网站上使用 svg 元素制作块的屏幕截图?

    我在网站上创建了一个构造函数 其本质是将所选元素及其颜色 svg中的元素 添加到访问者选择的背景和背景颜色 png中的背景 中 然后必须单击 保存 结果 按钮并仅执行工作区的屏幕截图 我写了这个脚本 但它需要屏幕截图 但只有背景 并忽略选定
  • ReactTransitionGroup 不适用于 React-redux 连接组件

    我正在开发一个更大的项目 但我创建了这个简短的示例来说明问题 如果我使用Box组件 它的工作原理 它在控制台中输出componentWillEnter and componentWillLeave当我们点击按钮时 如果我使用BoxConta
  • 如何计算特定字符在字符串中出现的次数

    我正在尝试创建一个函数来查看数组中的任何字符是否在字符串中 如果是 有多少个 我尝试计算每一种模式 但是太多了 我尝试使用 Python 中的 in 运算符的替代方案 但效果不佳 function calc fit element var
  • LeafleteachLayer函数不会迭代所有Layer

    使用 GeoJSON 数据数组创建一些标记 getJSON GetLocationsServlet function data L geoJSON data onEachFeature onEachFeature addTo mymap G
  • 如何使用 JavaScript 或 jQuery 克隆 HTML 元素的样式对象?

    我正在尝试克隆元素的样式对象 这应该允许我在更改后重置所述元素的样式 例如 el style left 50px curr style left 50px Modify the elements style The cloned style
  • Google Maps API (v3) 添加/更新标记

    编辑 它现在可以工作 但如果用户不允许或没有基于位置的服务 则不会加载 请参阅 jsfiddle 示例接受的答案评论 我已经浏览了一些教程和问题 但我无法安静地理解正在发生的事情 或者在这种情况下 没有发生 当用户单击链接时 我正在加载地图
  • 使用 Jade 评估自定义 javascript 方法 (CircularJSON)

    我想通过 Jade 将一个对象解析为客户端 JavaScript 通常这会起作用 script var object JSON parse JSON stringify object but my object is circular ht
  • Highcharts jQuery 渲染问题 - 所有浏览器

    我在尝试使用构建堆积柱形图时遇到了一个奇怪的问题高图表 http www highcharts com 当图表呈现时 在您调整浏览器大小之前 不会显示列无论如何 导致图表重绘 我认为 图表的其余部分显示 轴 标题等 但不显示列本身 我在 I
  • 使用 next.js 进行服务器端渲染与传统 SSR

    我非常习惯 SSR 意味着页面得到完全刷新并从服务器接收完整 HTML 的方法 其中根据后端堆栈使用 razor pub other 进行渲染 因此 每次用户单击导航链接时 它只会向服务器发送请求 整个页面将刷新 接收新的 HTML 这就是
  • Flot 库将 y 轴设置为最小值 0 和最大值 24

    如何将 y 轴设置在 0 到 24 的范围内 这是我的代码 j plot j placeholder d1 xaxis mode time min new Date 2010 11 01 getTime max new Date 2011
  • 将数组从 jquery ajax 传递到代码后面

    我必须将二维数组传递给在asp net网页代码后面编写的页面方法我有一个变量objList作为二维数组 我使用以下代码来实现此目的 但没有成功 并且未调用页面方法 脚本语言 function BindTable objList ajax u
  • 仅当显式选择行时才关闭 ui-bootstrap typeahead

    我创建了这个jsBin http jsbin com livuqafe 2 edit来证明我遇到的问题 如果您转到此处 请尝试输入 五 并继续 你的自然反应是输入 五 然后按 Tab 如果你想要 五百 你可以向下箭头一次 但是 在这种情况下
  • 如何通过索引访问 JSON 对象中的字段

    我知道这不是最好的方法 但我别无选择 我必须通过索引访问 JSONObject 中的项目 访问对象的标准方法是只写this objectName or this objectName 我还找到了一种获取 json 对象内所有字段的方法 fo

随机推荐

  • 如何把VRTE的应用程序在Ubuntu上跑起来?

    1 rvbuild d project name 20 产生一个文件夹vrte 如果此时直接将vrte文件夹打包放到Ubuntu的 opt目录内 执行 opt vrte usr bin exmd sh 会出现如下错误 但是查看文件发现这个文
  • 电感的主要参数

    转载 大鑫专栏 大鑫专栏 2022 02 13 21 08 电感的主要参数 1 电感量 L 又称自感系数 表示电感器产生自感能力的一个物理量 电感器电感量的大小 主要取决于线圈的圈数 匝数 绕制方式 有无磁心及磁心的材料等等 当通过一个线圈
  • [798]操作无法完成,因为文件已在另一个程序中打开

    我们在对文件或文件夹进行删除 移动 重命名等操作时 系统可能提示 操作无法完成 因为其中的文件夹已在另一程序中打开 请关闭该文件或文件夹 然后重试 遇到这种情况我们应该怎么办呢 请看下文 当我们对文件进行重命名 删除或者是移动处理的时候 有
  • Uboot命令使用

    一 uboot启动log简析 1 以后带有调试性质的开发 uboot都是烧写到SD卡中的 因为方便烧写 二 uboot命令使用 2 1 help命令 查看某一个命令帮助信息 命令名 2 2 信息查询 1 bdinfo 2 printenv命
  • Testing the CATCHER

    http poj org problem id 1887Description A military contractor for the Department of Defense has just completed a series
  • ide 安装eval reset插件

    1 安装eval reset的目的 Jetbrains家的产品有一个很良心的地方 他会允许你试用30天 这个数字写死在代码里了 以评估是否你真的需要为它而付费 事实上有一款插件可以实现这个功能 你或许可以用它来重置一下试用时间 但切记不要无
  • openGL之API学习(五十七)法线贴图、色彩贴图、高光贴图

    Normal map Normal map 法线贴图 它的作用是模拟出高模上的一些细节纹理 特别是将高模上的圆滑和粗糙度投射到低模上 让低模也有高模的效果 因为高模的面数非常多 导入引擎后电脑是跑不动的 所以用低模加上法线贴图就能很好的解决
  • Golang 结构化日志包 log/slog 详解(二):Handler

    上一篇文章介绍了推出 log slog 包的背景 log slog 包的简单介绍和使用 简单使用了 Info 函数 例如 package main import log slog func main slog Info hello 标题 路
  • 深入理解搜索引擎优化(SEO)

    深入理解搜索引擎优化 深入理解搜索引擎优化 SEO 1 SEO基础入门 SEO概述 搜索引擎 营销策略 SEO查询工具与站长平台 收录与权重 2 SEO站内优化 关键词 域名 主机 程序与SEO 设计技巧及优化 标签优化技巧 TDK 页面关
  • 三层交换机实现VLAN间通信配置实验(交换机/路由器配置与管理任务教程)网络设备管理

    三层交换机实现VLAN间通信配置实验 1 首先我在这里分享思科模拟器 中文版 和这次实验的参考文件里面有我自己配置和详细的描述讲解 有需要的同学可以自己下载 链接 https pan baidu com s 1y8He1E5RJKLkV l
  • 虚拟机 系统镜像 下载地址

    1 最完整的系统镜像 为了装个虚拟机 也跳了很多坑 感觉骗子都学会上网了 几次被整懵圈 坚决不要下载第三方的杂七杂八的系统镜像 不能使用都罢了 一旦使用 好像他们祖祖辈辈都被暗暗的安装到了系统中 全部为放心使用版本 良心推荐 http ww
  • 小白入门C#编写MVC登录小案例

    一 C 编写MVC登录小案例 1 新建MVC项目 2 在Models文件夹下创建一个User类 包含登录所需要的用户名和密码属性 namespace MvcLogin Models public class User public stri
  • Python深度学习篇四《机器学习基础》

    前言 前期回顾 Python深度学习篇三 神经网络入门 上面这篇里面写了关于向量数据最常见的机器学习任务 好 接下来切入正题 本章包括以下内容 除分类和回归之外的机器学习形式 评估机器学习模型的规范流程 为深度学习准备数据 特征工程 解决过
  • 「AI初识境」深度学习中常用的损失函数有哪些?

    https www toutiao com a6695152940425937411 这是专栏 AI初识境 的第11篇文章 所谓初识 就是对相关技术有基本了解 掌握了基本的使用方法 今天来说说深度学习中常见的损失函数 loss 覆盖分类 回
  • 代码审查清单

    代码审查清单 常规项 代码能够工作么 它有没有实现预期的功能 逻辑是否正确等 所有的代码是否简单易懂 代码符合你所遵循的编程规范么 这通常包括大括号的位置 变量名和函数名 行的长度 缩进 格式和注释 是否存在多余的或是重复的代码 代码是否尽
  • 在开发中,我们需要对后端返回来的数据进行转型,后端返回的字符串类型,布尔类型,进行使用

    后端返回 num 12 598 num2 11 222 这种字符串的数字 在前端需要根据这个数字来做一些判断的时候 直接 if num gt num2 console log 方法 这样子操作 很明显是不可以的 所以我们需要进行转换数据类型
  • wsl ubuntu拒绝访问_用WSL轻松实现WinLinux双系统

    在很多的情况下 我们对Linux具有不可替代的需求 但Ps Pr等工具的适用场景也使得我们不能抛弃Windows 面对此种情状 大部分人要么会选择使用虚拟机 要么则会选择双系统 但事实上 过于臃肿的虚拟机系统会使得硬件资源的占用率非常高 进
  • personal Richard photo picture

  • 更新Android studio后 sdk没有tools目录,无法使用device monitor

    谷歌在较新版本的android studio内去除了DDMS的支持 用其他功能代替 而日常开发测试中 Device Monitor的应用还是非常方便的 那么 问题来了 如何继续使用DDMS这款调试神器 如何在谷歌已经弃用的情况下 重新安装a
  • ES6入门八:Promise异步编程与模拟实现源码

    Promise的基本使用入门 实例化promise对象与注册回调 宏任务与微任务的执行顺序 then方法的链式调用与抛出错误 throw new Error 链式调用的返回值与传值 Promise的基本使用进阶 then catch fin