Products
GG网络技术分享 2025-03-18 16:14 0
作者:iNahoo
转发链接:https://mp.weixin.qq.com/s/5TxPjznpEBku_ybSMBdnfw
基于Canvas实现的高斯模糊(上)「JS篇」
基于Canvas实现的高斯模糊(下)「JS篇」本篇
小编建议小伙们从第一篇开始,按照顺序来看,更清晰明了。
考虑到来自服务端的图片可能有各种神奇的尺寸,而通常输出是一个确定的尺寸。
在这样的情况下,缩小比例会产生一些冗余,所以更适合另一个【锁定输出宽高的实现】。
/**
*@public
*暴露的异步模糊方法
*---------------------
*@paramURL图片地址,需要跨域支持
*@paramr模糊半径{Int}
*@paramw输出宽度{Number}
*@paramh输出高度{Number}
*@return{Promise}
*/
exportconstblurWH=(URL,r,w,h)=>{
returnnewPromise((resolve,reject)=>{
constIMG=newImage();
IMG.crossOrigin='*';//需要图片跨域支持
IMG.onload=function(){
constCanvas=document.createElement('CANVAS');//大量使用可考虑只创建一次
//锁定输出宽高之后,就不需要Care原图有多宽多高了
//letw=IMG.width,h=IMG.height;
//因为懒,就全Try了,实际上只Try跨域错误即可
try{
//设置Canvas宽高,获取上下文
Canvas.width=w;
Canvas.height=h;
letctx=Canvas.getContext('2d');
ctx.drawImage(IMG,0,0,w,h);
//提取图片信息
letd=ctx.getImageData(0,0,w,h);
//进行高斯模糊
letgd=gaussBlur(d,r,0);
//绘制模糊图像
ctx.putImageData(gd,0,0);
resolve(Canvas.toDataURL());
}catch(e){
reject(e);
}
};
IMG.src=URL;
})
};
V8对连续执行的代码有静态优化,所以文中所列时间大家不要较真,看个数量级就好 ╮(╯▽╰)╭
/**
*@fileOverview
*高斯模糊
*@authoriNahoo
*@since2017/5/8.
*/
"usestrict";
constgaussBlur=function(imgData,radius){
radius*=3;//不知为什么,我的模糊半径是 css中 filter:bulr 值的三倍时效果才一致。
//Copy图片内容
letpixes=newUint8ClampedArray(imgData.data);
constwidth=imgData.width;
constheight=imgData.height;
letgaussMatrix=[],
gaussSum,
x,y,
r,g,b,a,
i,j,k,
w;
radius=Math.floor(radius);
constsigma=radius/3;
a=1/(Math.sqrt(2*Math.PI)*sigma);
b=-1/(2*sigma*sigma);
//生成高斯矩阵
for(i=-radius;i<=radius;i++){
gaussMatrix.push(a*Math.exp(b*i*i));
}
//x方向一维高斯运算
for(y=0;y<height;y++){
for(x=0;x<width;x++){
r=g=b=a=gaussSum=0;
for(j=-radius;j<=radius;j++){
k=x+j;
if(k>=0&&k<width){
i=(y*width+k)*4;
w=gaussMatrix[j+radius];
r+=pixes[i]*w;
g+=pixes[i+1]*w;
b+=pixes[i+2]*w;
a+=pixes[i+3]*w;
gaussSum+=w;
}
}
i=(y*width+x)*4;
//计算加权均值
imgData.data.set([r,g,b,a].map(v=>v/gaussSum),i);
}
}
pixes.set(imgData.data);
//y方向一维高斯运算
for(x=0;x<width;x++){
for(y=0;y<height;y++){
r=g=b=a=gaussSum=0;
for(j=-radius;j<=radius;j++){
k=y+j;
if(k>=0&&k<height){
i=(k*width+x)*4;
w=gaussMatrix[j+radius];
r+=pixes[i]*w;
g+=pixes[i+1]*w;
b+=pixes[i+2]*w;
a+=pixes[i+3]*w;
gaussSum+=w;
}
}
i=(y*width+x)*4;
imgData.data.set([r,g,b,a].map(v=>v/gaussSum),i);
}
}
returnimgData;
};
/**
*@public
*暴露的异步模糊方法
*---------------------
*@paramURL图片地址,需要跨域支持
*@paramr模糊半径{Int}
*@paramshrink缩小比率{Number}
*@return{Promise}
*/
exportconstblur=(URL,r,shrink=1)=>{
returnnewPromise((resolve,reject)=>{
constIMG=newImage();
IMG.crossOrigin='*';//需要图片跨域支持
IMG.onload=function(){
constCanvas=document.createElement('CANVAS');//大量使用可考虑只创建一次
letw=IMG.width,h=IMG.height;
//缩小比例不为1时,重新计算宽高比
if(shrink!==1){
w=Math.ceil(w/shrink);
h=Math.ceil(h/shrink);
r=Math.ceil(r/shrink);
}
//因为懒,就全Try了,实际上只Try跨域错误即可
try{
//设置Canvas宽高,获取上下文
Canvas.width=w;
Canvas.height=h;
letctx=Canvas.getContext('2d');
ctx.drawImage(IMG,0,0,w,h);
//提取图片信息
letd=ctx.getImageData(0,0,w,h);
//进行高斯模糊
letgd=gaussBlur(d,r,0);
//绘制模糊图像
ctx.putImageData(gd,0,0);
resolve(Canvas.toDataURL());
}catch(e){
reject(e);
}
};
IMG.src=URL;
})
};
/**
*@public
*暴露的异步模糊方法
*---------------------
*@paramURL图片地址,需要跨域支持
*@paramr模糊半径{Int}
*@paramw输出宽度{Number}
*@paramh输出高度{Number}
*@return{Promise}
*/
exportconstblurWH=(URL,r,w,h)=>{
returnnewPromise((resolve,reject)=>{
constIMG=newImage();
IMG.crossOrigin='*';//需要图片跨域支持
IMG.onload=function(){
constCanvas=document.createElement('CANVAS');//大量使用可考虑只创建一次
//锁定输出宽高之后,就不需要Care原图有多宽多高了
//letw=IMG.width,h=IMG.height;
//因为懒,就全Try了,实际上只Try跨域错误即可
try{
//设置Canvas宽高,获取上下文
Canvas.width=w;
Canvas.height=h;
letctx=Canvas.getContext('2d');
ctx.drawImage(IMG,0,0,w,h);
//提取图片信息
letd=ctx.getImageData(0,0,w,h);
//进行高斯模糊
letgd=gaussBlur(d,r,0);
//绘制模糊图像
ctx.putImageData(gd,0,0);
resolve(Canvas.toDataURL());
}catch(e){
reject(e);
}
};
IMG.src=URL;
})
};本篇已完结
《由浅入深,66条JavaScript面试知识点(一)》
《由浅入深,66条JavaScript面试知识点(二)》
《由浅入深,66条JavaScript面试知识点(三)》
《由浅入深,66条JavaScript面试知识点(四)》
《由浅入深,66条JavaScript面试知识点(五)》
《由浅入深,66条JavaScript面试知识点(六)》
《由浅入深,66条JavaScript面试知识点(七)》
《为什么 setTimeout 有最小时延 4ms ?》
《如何处理 Node.js 中出现的未捕获异常?》
《Angular v10.0.0 正式发布,不再支持 IE9/10》
《基于 Docker 的 SSR 持续开发集成环境实践》
《细聊图解webpack 指南手册》
《一文带你彻底搞懂 NPM 知识点「进阶篇」》
《细聊webpack性能优化面面观》
《JS实现各种日期操作方法汇总》
《「实践」细聊前端性能优化总结》
《「实践」浏览器中的画中画(Picture-in-Picture)模式及其 API》
《「多图」一文带你彻底搞懂 Web Workers (上)》
《「多图」一文带你彻底搞懂 Web Workers (中)》
《深入细聊前端下载总结「干货」》
《细品西瓜播放器功能分析(上)「实践」》
《细品西瓜播放器功能分析(下)「实践」》
《细聊50道JavaScript基础面试题「附答案」》
《webpack4主流程源码解说以及动手实现一个简单的webpack(上)》
《webpack4主流程源码解说以及动手实现一个简单的webpack(下)》
《细聊前端架构师的视野》
《细聊应用场景再谈防抖和节流「进阶篇」》
《前端埋点统一接入方案实践》
《细聊微内核架构在前端的应用「干货」》
《一种高性能的Tree组件实现方案「干货」》
《进击的JAMStack》
《前后端全部用 JS 开发是什么体验(Hybrid + Egg.js经验分享)上》
《前后端全部用 JS 开发是什么体验(Hybrid + Egg.js经验分享)中》
《前后端全部用 JS 开发是什么体验(Hybrid + Egg.js经验分享)下》
《一文带你搞懂 babel-plugin-import 插件(上)「源码解析」》
《一文带你搞懂 babel-plugin-import 插件(下)「源码解析」》
《JavaScript常用API合集汇总「值得收藏」》
《推荐10个常用的图片处理小帮手(上)「值得收藏」》
《推荐10个常用的图片处理小帮手(下)「值得收藏」》
《JavaScript 中ES6代理的实际用例》
《12 个实用的前端开发技巧总结》
《一文带你搞懂搭建企业级的 npm 私有仓库》
《教你如何使用内联框架元素 IFrames 的沙箱属性提高安全性?》
《细说前端开发UI公共组件的新认识「实践」》
《细说DOM API中append和appendChild的三个不同点》
《细品淘系大佬讲前端新人如何上王者「干货」》
《一文带你彻底解决背景跟随弹窗滚动问题「干货」》
《推荐常用的5款代码比较工具「值得收藏」》
《Node.js实现将文字与图片合成技巧》
《爱奇艺云剪辑Web端的技术实现》
《我再也不敢说我会写前端 Button组件「实践」》
《NodeX Component - 滴滴集团 Node.js 生态组件体系「实践」》
《Node Buffers 完整指南》
《推荐18个webpack精美插件「干货」》
《前端开发需要了解常用7种JavaScript设计模式》
《浅谈浏览器架构、单线程js、事件循环、消息队列、宏任务和微任务》
《了不起的 Webpack HMR 学习指南(上)「含源码讲解」》
《了不起的 Webpack HMR 学习指南(下)「含源码讲解」》
《10个打开了我新世界大门的 WebAPI(上)「实践」》
《10个打开了我新世界大门的 WebAPI(中)「实践」》
《10个打开了我新世界大门的 WebAPI(下)「实践」》
《「图文」ESLint 在中大型团队的应用实践》
《Deno是代码的浏览器,你认同吗?》
《前端存储除了 localStorage 还有啥?》
《Javascript 多线程编程的前世今生》
《微前端方案 qiankun(实践及总结)》
《「图文」V8 垃圾回收原来这么简单?》
《Webpack 5模块联邦引发微前端的革命?》
《基于 Web 端的人脸识别身份验证「实践」》
《「前端进阶」高性能渲染十万条数据(时间分片)》
《「前端进阶」高性能渲染十万条数据(虚拟列表)》
《图解 Promise 实现原理(一):基础实现》
《图解 Promise 实现原理(二):Promise 链式调用》
《图解 Promise 实现原理(三):Promise 原型方法实现》
《图解 Promise 实现原理(四):Promise 静态方法实现》
《实践教你从零构建前端 Lint 工作流「干货」》
《高性能多级多选级联组件开发「JS篇」》
《深入浅出讲解Node.js CLI 工具最佳实战》
《延迟加载图像以提高Web网站性能的五种方法「实践」》
《比较 JavaScript 对象的四种方式「实践」》
《使用Service Worker让你的 Web 应用如虎添翼(上)「干货」》
《使用Service Worker让你的 Web 应用如虎添翼(中)「干货」》
《使用Service Worker让你的 Web 应用如虎添翼(下)「干货」》
《前端如何一次性处理10万条数据「进阶篇」》
《推荐三款正则可视化工具「JS篇」》
《如何让用户选择是否离开当前页面?「JS篇」》
《JavaScript开发人员更喜欢Deno的五大原因》
《仅用18行JavaScript实现一个倒数计时器》
《图文细说JavaScript 的运行机制》
《一个轻量级 JavaScript 全文搜索库,轻松实现站内离线搜索》
《推荐Web程序员常用的15个源代码编辑器》
《10个实用的JS技巧「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(一)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(二)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(三)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(四)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(五)「值得收藏」》
《细品269个JavaScript小函数,让你少加班熬夜(六)「值得收藏」》
《深入JavaScript教你内存泄漏如何防范》
《手把手教你7个有趣的JavaScript 项目-上「附源码」》
《手把手教你7个有趣的JavaScript 项目-下「附源码」》
《JavaScript 使用 mediaDevices API 访问摄像头自拍》
《手把手教你前端代码如何做错误上报「JS篇」》
《一文让你彻底搞懂移动前端和Web 前端区别在哪里》
《63个JavaScript 正则大礼包「值得收藏」》
《提高你的 JavaScript 技能10 个问答题》
《JavaScript图表库的5个首选》
《一文彻底搞懂JavaScript 中Object.freeze与Object.seal的用法》
《可视化的 JS:动态图演示 - 事件循环 Event Loop的过程》
《教你如何用动态规划和贪心算法实现前端瀑布流布局「实践」》
《可视化的 js:动态图演示 Promises & Async/Await 的过程》
《原生JS封装拖动验证滑块你会吗?「实践」》
《如何实现高性能的在线 PDF 预览》
《细说使用字体库加密数据-仿58同城》
《Node.js要完了吗?》
《Pug 3.0.0正式发布,不再支持 Node.js 6/8》
《纯JS手写轮播图(代码逻辑清晰,通俗易懂)》
《JavaScript 20 年 中文版之创立标准》
《值得收藏的前端常用60余种工具方法「JS篇」》
《箭头函数和常规函数之间的 5 个区别》
《通过发布/订阅的设计模式搞懂 Node.js 核心模块 Events》
《「前端篇」不再为正则烦恼》
《「速围」Node.js V14.3.0 发布支持顶级 Await 和 REPL 增强功能》
《深入细品浏览器原理「流程图」》
《JavaScript 已进入第三个时代,未来将何去何从?》
《前端上传前预览文件 image、text、json、video、audio「实践」》
《深入细品 EventLoop 和浏览器渲染、帧动画、空闲回调的关系》
《推荐13个有用的JavaScript数组技巧「值得收藏」》
《前端必备基础知识:window.location 详解》
《不要再依赖CommonJS了》
《犀牛书作者:最该忘记的JavaScript特性》
《36个工作中常用的JavaScript函数片段「值得收藏」》
《Node + H5 实现大文件分片上传、断点续传》
《一文了解文件上传全过程(1.8w字深度解析)「前端进阶必备」》
《【实践总结】关于小程序挣脱枷锁实现批量上传》
《手把手教你前端的各种文件上传攻略和大文件断点续传》
《字节跳动面试官:请你实现一个大文件上传和断点续传》
《谈谈前端关于文件上传下载那些事【实践】》
《手把手教你如何编写一个前端图片压缩、方向纠正、预览、上传插件》
《最全的 JavaScript 模块化方案和工具》
《「前端进阶」JS中的内存管理》
《JavaScript正则深入以及10个非常有意思的正则实战》
《前端面试者经常忽视的一道JavaScript 面试题》
《一行JS代码实现一个简单的模板字符串替换「实践」》
《JS代码是如何被压缩的「前端高级进阶」》
《前端开发规范:命名规范、html规范、css规范、js规范》
《【规范篇】前端团队代码规范最佳实践》
《100个原生JavaScript代码片段知识点详细汇总【实践】》
《关于前端174道 JavaScript知识点汇总(一)》
《关于前端174道 JavaScript知识点汇总(二)》
《关于前端174道 JavaScript知识点汇总(三)》
《几个非常有意思的javascript知识点总结【实践】》
《都2020年了,你还不会JavaScript 装饰器?》
《JavaScript实现图片合成下载》
《70个JavaScript知识点详细总结(上)【实践】》
《70个JavaScript知识点详细总结(下)【实践】》
《开源了一个 JavaScript 版敏感词过滤库》
《送你 43 道 JavaScript 面试题》
《3个很棒的小众JavaScript库,你值得拥有》
《手把手教你深入巩固JavaScript知识体系【思维导图】》
《推荐7个很棒的JavaScript产品步骤引导库》
《Echa哥教你彻底弄懂 JavaScript 执行机制》
《一个合格的中级前端工程师需要掌握的 28 个 JavaScript 技巧》
《深入解析高频项目中运用到的知识点汇总【JS篇】》
《JavaScript 工具函数大全【新】》
《从JavaScript中看设计模式(总结)》
《身份证号码的正则表达式及验证详解(JavaScript,Regex)》
《浏览器中实现JavaScript计时器的4种创新方式》
《Three.js 动效方案》
《手把手教你常用的59个JS类方法》
《127个常用的JS代码片段,每段代码花30秒就能看懂-【上】》
《深入浅出讲解 js 深拷贝 vs 浅拷贝》
《手把手教你JS开发H5游戏【消灭星星】》
《深入浅出讲解JS中this/apply/call/bind巧妙用法【实践】》
《手把手教你全方位解读JS中this真正含义【实践】》
《书到用时方恨少,一大波JS开发工具函数来了》
《干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)》
《手把手教你JS 异步编程六种方案【实践】》
《让你减少加班的15条高效JS技巧知识点汇总【实践】》
《手把手教你JS开发H5游戏【黄金矿工】》
《手把手教你JS实现监控浏览器上下左右滚动》
《JS 经典实例知识点整理汇总【实践】》
《2.6万字JS干货分享,带你领略前端魅力【基础篇】》
《2.6万字JS干货分享,带你领略前端魅力【实践篇】》
《简单几步让你的 JS 写得更漂亮》
《恭喜你获得治疗JS this的详细药方》
《谈谈前端关于文件上传下载那些事【实践】》
《面试中教你绕过关于 JavaScript 作用域的 5 个坑》
《Jquery插件(常用的插件库)》
《【JS】如何防止重复发送ajax请求》
《JavaScript+Canvas实现自定义画板》
《Continuation 在 JS 中的应用「前端篇」》
作者:iNahoo
转发链接:https://mp.weixin.qq.com/s/5TxPjznpEBku_ybSMBdnfw
Promise是一个管理异步编程的方案,它是一个构造函数,每次使用可用new创建实例;它有三种状态:pending、fulfilled和rejected,这三种状态不会受外界影响,状态只能由pending变为fullfilled(成功),pending变为rejected(失败),且一旦改变就不会再改变,在状态改变后,它会返回成功的结果或者失败的原因,它对外抛出了resolve、reject、catch、finally、then、all、race、done,在最新的提案中,添加了allSettled方法,它不管成功、失败都会返回,接下来,我们自己实现整个Promise
executor函数
我们知道,在创建一个Promise实例时,都会立即执行executor函数,executor函数传递两个参数,resolve和reject,如果executor函数执行错误,Promise实例状态会变为rejected
class MyPromise{ constructor(executor) { this.status = \"pending\"; // 初始化状态为pending this.value = undefined; // 初始化返回的成功的结果或者失败的原因 // 这里是resolve方法,成功后执行,将状态改变为resolved,并且将结果返回 let resolve = result => { if(this.status !== \"pending\") return; // 状态一旦改变,就不会再变 this.status = \"resolved\"; this.value = result; } // 这里是reject方法,异常时执行,状态改为rejected,并且将失败的原因返回 let reject = reason => { if(this.status !== \"pending\") return; this.status = \"rejected\"; this.value = reason; } // try、catch捕获异常,如果错误,执行reject方法 try { executor(resolve, reject) } catch(err) { reject(err) } } } |
我们来验证一下,现在的Promise是什么样的
let p1 = new MyPromise((resolve, reject) => { resolve(1); }) let p2 = new MyPromise((resolve, reject) => { reject(2); }) console.log(p1); console.log(p2); |
可以看到,状态已经改变了,里面的值也是成功的结果和失败的原因。then方法有两个参数,第一个参数是成功时执行的,第二个参数为失败后执行的,then的链式调用和数组等是一样的,每次执行后会返回一个Promise实例。如果成功后,第一个then中成功的函数为null,它会继续向下查找,直至不为null的函数执行,上一个then中返回的结果会直接影响下一个then中执行成功或者失败的哪个函数,了解了这些之后,我们尝试实现一下~
then方法
then(resolveFn, rejectFn) { // 如果传入的两个参数不是函数,则直接执行返回结果 let resolveArr = []; let rejectArr = []; if(typeof resolveFn !== \"function\") { resolveFn = result => { return result; } } if(typeof rejectFn !== \"function\") { rejectFn = reason => { return MyPromise.reject(reason); } } return new Mypromise((resolve, reject) => { resolveArr.push(result => { try { let x = resolveFn(result); if(x instanceof MyPromise) { x.then(resolve, reject) return; } resolve(x); } catch(err) { reject(err) } }) rejectArr.push(reason => { try { let x = rejectFn(reason); if(x instanceof MyPromise) { x.then(resolve, reject) return; } resolve(x); } catch(err) { reject(err) } }) }) } |
我们来整理一下上面的代码
class MyPromise{ constructor(executor) { this.status = \"pending\"; // 初始化状态为pending this.value = undefined; // 初始化返回的成功的结果或者失败的原因 this.resolveArr = []; // 初始化then中成功的方法 this.rejectArr = []; // 初始化then中失败的方法 // 定义change方法,因为我们发现好像resolve和reject方法共同的地方还挺多 let change = (status, value) => { if(this.status !== \"pending\") return; // 状态一旦改变,就不会再变 this.status = status; this.value = value; // 根据状态判断要执行成功的方法或失败的方法 let fnArr = status === \"resolved\" ? this.resolveArr : this.rejectArr; // fnArr中的方法依次执行 fnArr.forEach(item => { if(typeof item !== \"function\") return; item(this. value); }) } // 这里是resolve方法,成功后执行,将状态改变为resolved,并且将结果返回 let resolve = result => { change(\"resolved\", result) } // 这里是reject方法,异常时执行,状态改为rejected,并且将失败的原因返回 let reject = reason => { change(\"rejected\", reason); } // try、catch捕获异常,如果错误,执行reject方法 try { executor(resolve, reject) } catch(err) { reject(err) } } then(resolveFn, rejectFn) { // 如果传入的两个参数不是函数,则直接执行返回结果 if(typeof resolveFn !== \"function\") { resolveFn = result => { return result; } } if(typeof rejectFn !== \"function\") { rejectFn = reason => { return MyPromise.reject(reason); } } return new MyPromise((resolve, reject) => { this.resolveArr.push(result => { try { let x = resolveFn(result); // 获取执行成功方法返回的结果 // 如果x是一个promise实例,则继续调用then方法 ==> then链的实现 if(x instanceof MyPromise) { x.then(resolve, reject) return; } // 不是promise实例,直接执行成功的方法 resolve(x); } catch(err) { reject(err) } }) this.rejectArr.push(reason => { try { let x = rejectFn(reason); if(x instanceof MyPromise) { x.then(resolve, reject) return; } resolve(x); } catch(err) { reject(err) } }) }) } } |
我们来看一下效果
new MyPromise((resolve, reject) => { resolve(1); }).then(res => { console.log(res, \'success\'); }, err => { console.log(err, \'error\'); }) |
这时候,问题出现了,我们发现好像什么也没有输出,如果我们对上面的测试例子做一下小小的改动呢?
new MyPromise((resolve, reject) => { setTimeout(_ => { resolve(1); }, 0) }).then(res => { console.log(res, \'success\'); // 1 \"success\" }, err => { console.log(err, \'error\'); }) |
这是因为创建了Promise实例就立即执行了executor函数,还没有执行then方法,那么不管成功还是失败的数组中,都是空的。那可能小伙伴们又有疑问了,为什么加了setTimeout就好使了呢?这是因为在事件队列机制中,setTimeout会放入事件队列中,等主线程执行完成后再执行,此时then方法会存储成功或者失败的函数,所以不管是成功的数组还是失败的数组中都已经有值了,这个时候再去执行就完全了~
但是我们不能在使用的时候写setTimeout当做解决方案呀,既然我们在封装,就要在封装的函数内解决问题,按照这样的思路,我们也同样可以在resolve和reject方法执行的时候,判断数组中是否有值,如果没有,我们可以利用setTimeout让它延后执行,代码如下~
// 这里是resolve方法,成功后执行,将状态改变为resolved,并且将结果返回 let resolve = result => { // 如果数组中有值,则立即改变状态 if(this.resolveArr.length > 0) { change(\"resolved\", result) } // 如果没值,则延后改变状态 let timer = setTimeout(_ => { change(\"resolved\", result) clearTimeout(timer); }, 0) } // 这里是reject方法,异常时执行,状态改为rejected,并且将失败的原因返回 let reject = reason => { // 如果数组中有值,则立即改变状态 if(this.rejectArr.length > 0) { change(\"rejected\", reason); } // 如果没值,则延后改变状态 let timer = setTimeout(_ => { change(\"rejected\", reason); clearTimeout(timer); }, 0) } |
现在我们再试一下
// 1、已经成功了 new MyPromise((resolve, reject) => { resolve(\'我成功啦,吼吼吼~~~~\'); reject(\'我都已经成功了,你别想让我失败,哼~~\'); }).then(res => { console.log(res, \'success\'); // 我成功啦,吼吼吼~~~~ success }, err => { console.log(err, \'error\'); }) // 2、先失败了 new MyPromise((resolve, reject) => { reject(\'失败了,我好委屈,呜呜呜~~\'); resolve(\'已经失败了~~~\'); }).then(res => { console.log(res, \'success\'); }, err => { console.log(err, \'error\'); // 失败了,我好委屈,呜呜呜~~ error }) // 3、链式调用 new MyPromise((resolve, reject) => { reject(\'失败了,我好委屈,呜呜呜~~\'); resolve(\'已经失败了~~~\'); }).then(res => { console.log(res); }, err => { console.log(err, \'error\'); // 失败了,我好委屈,呜呜呜~~ error return \'我要发奋图强,不会被困难所击倒,我要成功!!!\' }).then(res1 => { console.log(res1, \'经过不懈努力,我终于在第二次成功了~\'); // 我要发奋图强,不会被困难所击倒,我要成功!!! 经过不懈努力,我终于在第二次成功了~ }, err1 => { console.log(err1, \'第二次失败\'); }) |
这就完美解决了第一次调用,不会执行then方法的问题。同时,实现了链式的调用。对于链式的调用,我多啰嗦两句,其实不管是数组的链式调用,都是因为上一次返回的还是此实例。
catch方法
catch方法是捕获异常,它和then方法的第二个回调函数是一样的
catch(rejectFn) { return this.then(null, rejectFn) } |
resolve方法
我们知道,Promsie也可以这样用
let p1 = MyPromise.resolve(1); console.log(p1); |
我们期望有这样一种写法,但是现在肯定会抛出错误:MyPromise.resolve不是一个方法
现在需要我们封装一下resolve方法,我们需要明确的是,resolve之后,Promise是支持再继续链式调用then的,所以,我们需要执行resolve方法,返回一个Promise实例
static resolve(result) { // 返回新的promise实例,执行promise实例中resolve方法 return new MyPromise(resolve => { resolve(result) }) } |
reject方法
像resolve方法一样,只不过它接收的是失败的函数
static reject(reason) { // 返回新的promise实例,执行promise实例中reject方法 return new MyPromise((_, reject) => { reject(reason); }) } |
done方法
ES6标准入门一书中,对done方法的解释是这样的:无论Promise对象的回调链以then方法还是catch方法结尾,只要最后一个方法抛出错误,都有可能无法捕获到。为此,Promise提供了一个done方法,它总是处于回掉链的尾端,保证抛出任何可能出现的错误。好了,我们知道了这个方法是干啥的,现在就开始写吧~
done(resolveFn, rejectFn) { this.then(resolveFn, rejectFn) .catch(reason => { setTimeout(() => { throw reason; }, 0) }) } |
它可以接收fulfilled、rejected状态的回调函数,也可以不提供任何参数。但是无论怎样,done方法都会捕捉到任何可能出现的错误,并向全局抛出
finally方法
finally方法是无论成功还是失败都会执行的方法,像这样的方法还有小程序中的complete方法等等,我们来尝试实现一下~
finally(finallyFn) { let P = this.constructor; return this.then( value => P.resolve(finallyFn()).then(() => value), reason => P.reject(finallyFn()).then(() => reason) ) } |
我们来验证一下
new MyPromise((resolve, reject) => { reject(\'失败了,我好委屈,呜呜呜~~\'); resolve(\'已经失败了~~~\'); }).then(res => { console.log(res); }, err => { console.log(err, \'error\'); // 失败了,我好委屈,呜呜呜~~ error return \'我要发奋图强,不会被困难所击倒,我要成功!!!\' }).finally(() => { console.log(\'执行了吗\'); // 这里会输出\"执行了吗\" }) |
all方法
all方法接收一个数组,当数组中每个实例都成功时才会返回,返回的也是一个数组,每个参数为对应的promise返回的结果,如果有一项失败了,all方法都会返回失败
// 接收数组参数 static all(promiseList) { // 返回新实例,调用后还可使用then、catch等方法 return new MyPromise((resolve, reject) => { let index = 0, // 成功次数计数 results = []; // 返回的结果 for(let i = 0; i < promiseList.length; i++) { let item = promiseList[i]; // 如果item不是promise实例 if(!(item instanceof MyPromise)) return; item.then(result => { index++; results[i] = result; if(index === promiseList.length) { resolve(results); } }).catch(reason => { reject(reason); }) } }) } |
来验证一下
// 1.有失败的情况 let p1 = MyPromise.resolve(1); let p2 = MyPromise.reject(2); let p3 = MyPromise.resolve(3); MyPromise.all([p1, p2, p3]) .then(res => { console.log(res); }).catch(err => { console.log(err, \'err\'); // 2 \"err\" }) // 2.无失败的情况 let p1 = MyPromise.resolve(1); let p2 = MyPromise.resolve(2); let p3 = MyPromise.resolve(3); MyPromise.all([p1, p2, p3]) .then(res => { console.log(res, \'success\'); // [1, 2, 3] \"success\" }).catch(err => { console.log(err, \'err\'); }) |
race方法
race方法同样接收一个数组参数,里面每一项是Promise实例,它返回最快改变状态的Promise实例方法的结果
static race(promiseList) { return new MyPromise((resolve, reject) => { promiseList.forEach(item => { if(!(item instanceof MyPromise)) return; item.then(result => { resolve(result); }).catch(err => { reject(err) }) }) }) } 复制代码验证 // 1. let p1 = MyPromise.resolve(1); let p2 = MyPromise.reject(2); let p3 = MyPromise.resolve(3); MyPromise.race([p1, p2, p3]) .then(res => { console.log(res); // 1 \'success\' }).catch(err => { console.log(err, \'err\'); }) // 2. let p1 = MyPromise.reject(1); let p2 = MyPromise.resolve(2); let p3 = MyPromise.resolve(3); MyPromise.race([p1, p2, p3]) .then(res => { console.log(res, \'success\'); }).catch(err => { console.log(err, \'err\'); // 1 \'err\' }) // 3. let p1 = MyPromise.reject(1); let p2 = MyPromise.reject(2); let p3 = MyPromise.reject(3); MyPromise.race([p1, p2, p3]) .then(res => { console.log(res, \'success\'); }).catch(err => { console.log(err, \'err\'); // 1 \'err\' }) |
尝试实现allSettled方法
allSettled方法也是接收数组参数,但是它无论成功或者失败,都会返回
static allSettled(promiseList) { return new MyPromise((resolve, reject) => { let results = []; for(let i = 0; i < promiseList.length; i++) { let item = promiseList[i]; if(!(item instanceof MyPromise)) return; item.then(result => { results[i] = result; }, reason => { results[i] = reason; }) resolve(results); } }) } 复制代码验证 // 1. let p1 = MyPromise.resolve(1); let p2 = MyPromise.resolve(2); let p3 = MyPromise.resolve(3); MyPromise.race([p1, p2, p3]) .then(res => { console.log(res); // [1, 2, 3] \'success\' }).catch(err => { console.log(err, \'err\'); }) // 2. let p1 = MyPromise.reject(1); let p2 = MyPromise.reject(2); let p3 = MyPromise.reject(3); MyPromise.race([p1, p2, p3]) .then(res => { console.log(res, \'success\'); // [1, 2, 3] \'success\' }).catch(err => { console.log(err, \'err\'); }) // 3. let p1 = MyPromise.resolve(1); let p2 = MyPromise.reject(2); let p3 = MyPromise.resolve(3); MyPromise.race([p1, p2, p3]) .then(res => { console.log(res, \'success\'); // [1, 2, 3] \'success\' }).catch(err => { console.log(err, \'err\'); }) |
以上就是手写JS实现Promise的详细内容,更多请关注网站的其它相关文章!
Demand feedback