1.从规范面解读:Promises/A+规范与浏览器Promise规范有何区别?
2.使用Sequelize快速构建PostgreSQL数据的CRUD操作详解
3.深入p-limit源码,如何限制并发数?
从规范面解读:Promises/A+规范与浏览器Promise规范有何区别?
前言
Promise是一种优秀的异步解决方案,其原生实现更是面试中的爆点,提到Promise实现,我们首先会想起Promises/A+规范,大多数教程中都是源码分享源码按照Promises/A+规范来实现Promise。
小包也是Promises/A+圣经的执行者之一,但小包心中一直有个好奇,遵循Promises/A+规范实现的Promise与ES6-Promise能有什么区别呐?
文章中的测试代码选取小包基于Promises/A+规范实现的原生Promise
学习本文,你能收获:
进一步完善原生Promise的实现
更进一步理解Promise与microTask之间的关系
promise的成功值valuePromises/A+规范只提供了value的定义,并没有详细说明如何处理不同类型的value值:
“value”isanylegalJavaScriptvalue(including?undefined,athenable,orapromise).value可以是任意合法的JavaScript值,包括undefined、具备then接口的对象或者promise
但ECMAScript规范对不同类型的value做了细致的处理。
红框部分我们可以看出,ES6规范会根据resolution(相当于Promises/A+规范中的value)类型选取不同的执行方案。
判断resolution是否为Object,如果不是,直接执行FulfillPromise
如果是Object,试探是否存在then接口
判断then是否可执行(abruptcompletion可以理解为非正常值)
如果then可执行,将then方法放入事件队列中。
PromiseResolveThenableJob:该job使用传入的thenable的then方法来解决promise。
一句话总结上面的as3933源码过程:如果value值为可thenable对象或者promise,ES6会采用该thenable的状态。
小包举个栗子:
const?p?=?new?Promise((resolve)?=>?{ resolve(1);});const?p1?=?new?Promise((resolve)?=>?{ resolve(p);});p1.then((d)?=>?console.log(d));p1接收的成功值value为Promisep,p状态为fulfilled,这种情况下ES6中会采取p的状态及value,因此最终打印1。
我们将p更换为具备thenable对象,结果也是类似的。
//?类?promise?对象const?p1?=?{ a:?1,then(onFulfilled,?onReject)?{ onFulfilled(this.a);},};const?p2?=?new?Promise((resolve)?=>?{ resolve(p1);});//?1p2.then((d)?=>?console.log(d));Promises/A+没有对此进行规范,因此当传入的value为thenable对象时,会原封不动的输出。
那我们应该如何完善这部分代码呐?我们需要对value值进行解析,如果value可thenable,则采纳他的状态和值,递归进行上述步骤,直至value不可thenable。(这里与resolvePromise部分递归解析onFulfilled函数的返回值是类似的)
const?resolve?=?(value)?=>?{ if?(typeof?value?===?"object"?&&?value?!=?null)?{ try?{ const?then?=?value.then;if?(typeof?then?===?"function")?{ return?then.call(value,?resolve,?reject);}}?catch?(e)?{ return?reject(e);}}if?(this.status?===?PENDING)?{ this.value?=?value;this.status?=?FULFILLED;this.onFulfilledCallbacks.forEach((cb)?=>?cb(this.value));}};Promise与microTaskPromises/A+规范中其实并没有将Promise对象与microTask挂钩,规范是这么说的:
Here“platformcode”meansengine,environment,andpromiseimplementationcode.Inpractice,thisrequirementensuresthat?onFulfilled?and?onRejected?executeasynchronously,aftertheeventloopturninwhich?then?iscalled,andwithafreshstack.Thiscanbeimplementedwitheithera“macro-task”mechanismsuchas?setTimeout?or?setImmediate,orwitha“micro-task”mechanismsuchas?MutationObserver?or?process.nextTick.Sincethepromiseimplementationisconsideredplatformcode,itmayitselfcontainatask-schedulingqueueor“trampoline”inwhichthehandlersarecalled.
Promises/A+规范中表示then方法可以通过setTimeout或setImediate等宏任务机制实现,也可以通过MutationObserver或process.nextTick等微任务机制实现。
但经过大量面试题洗礼的我们知道浏览器中的Promise.then典型的微任务。既然都学到这里了,小包索性就打破砂锅问到底,大漠 类模块 源码找到Promise与microTask挂钩的根源。
谁规定了Promise是microTask标准读起来属实有些无聊,但好在小包找到了最终的答案。
首先小包先入为主的以为,Promise的详细规定应该都位于ECMAScript制定的规范中,但当小包进入标准后,全局搜索micro,竟然只搜索到三个Microsoft。讲实话,小包是震惊的,ECMAScript并没有规定Promise是microTask。
ECMAScript规范中,最接近的是下面两段表达:
The?host-defined?abstractoperationHostEnqueuePromiseJobtakesarguments?job?(a?Job?AbstractClosure)and?realm?(a?RealmRecord?or?null)andreturns?unused.Itschedules?job?tobeperformedatsomefuturetime.The?AbstractClosures?usedwiththisalgorithmareintendedtoberelatedtothehandlingofPromises,orotherwise,tobescheduledwithequalprioritytoPromisehandlingoperations.
JobsarescheduledforexecutionbyECMAScripthostenvironments.ThisspecificationdescribesthehosthookHostEnqueuePromiseJobtoscheduleonekindofjob;hostsmaydefineadditionalabstractoperationswhichschedulejobs.SuchoperationsacceptaJobAbstractClosureastheparameterandscheduleittobeperformedatsomefuturetime.Theirimplementationsmustconformtothefollowingrequirements:
上面两句话意思大约是:ECMAScript中将Promise看作一个job(作业),HostEnqueuePromiseJob是用来调度Promise作业的方法,这个方法会在未来某个时间段执行,具体执行与Promise的处理函数或者与Promise处理操作相同的优先级有关。
那何处将Promise规定为microTask呐?---HTML标准
HTML标准中指出:
JavaScriptcontainsan?implementation-defined?HostEnqueuePromiseJob(job,?realm)abstractoperationtoschedulePromise-relatedoperations.HTMLschedulestheseoperationsinthemicrotaskqueue.
上述标准的最后一句话指出,HTML将在microqueue中安排这些操作。破案了,原来是HTML标准中将Promise规定为microTask。(为什么会是正则匹配网页源码HTML进行规定,小包还没有探究出来)
更深入的区别,请参考月夕大佬:V8Promise源码全面解读
后语我是?战场小包?,一个快速成长中的小前端,希望可以和大家一起进步。
如果喜欢小包,可以在?掘金?关注我,同样也可以关注我的小小公众号——小包学前端。
一路加油,冲向未来!!!
疫情早日结束人间恢复太平原文:/post/
使用Sequelize快速构建PostgreSQL数据的CRUD操作详解
之前写过一个专栏《布道API》来介绍API的REST风格及推荐实践,今天开始来构建一个管理系统的API服务,首先需要处理的就是数据存储,本文将结合实际开发总结在NodeJS下使用Sequelize快速构建PostgreSQL数据的CRUD操作。项目源代码:github.com/QuintionTang/pretender-service
SequelizeSequelize是一个基于promise的Node.jsORM工具,它具有强大的事务支持、关联关系、预读和延迟加载、读取复制等功能,支持的数据库包括:PostgreSQL、MySQL、MariaDB、SQLite和MSSQL。影视源码怎么用
Sequelize类是引用sequlize模块后获取一个顶级对象,通过它来创建sequlize实例,也可以通过该对象来获取模内其它对象的引用,如:Utils工具类、Transaction事务类等。创建实例后,可以通过实例来创建或定义Model(模型)、执行查询、同步数据库结构等操作。
官方网站:docs.sequelizejs.com/
添加和配置在安装模块之前,首先安装开发工具Sequelize-CLI
sudonpminstall-gsequelize-cli接下来在项目目录下安装数据存储相关的模块。
npminstallsequelize--savenpminstallpgpg-hstore--save现在在项目根目录下创建文件.sequelizerc,代码如下:
constpath=require('path');module.exports={ "config":path.resolve('./config','db.json'),"models-path":path.resolve('./models'),'seeders-path':path.resolve('./seeders'),'migrations-path':path.resolve('./migrations')};该文件将告诉Sequelize初始化,以生成config、models到特定目录。接下来,输入命令初始化Sequelize。
sequelizeinit该命令将创建config/db.json,models/index.js,migrations和seeders目录和文件。命令执行完毕之后打开并编辑config/db.json来配置数据库连接信息。
{ "development":{ "username":"dbusername","password":"dbpassword","database":"crayon-admin","host":".0.0.1","dialect":"postgres","options":{ "operatorsAliases":false},"logging":false},"test":{ "username":"dbusername","password":"dbpassword","database":"crayon-admin","host":".0.0.1","dialect":"postgres"},"production":{ "username":"dbusername","password":"dbpassword","database":"crayon-admin","host":".0.0.1","dialect":"postgres"}}目录说明:
migrations:所有迁移文件,通过sequelizedb:migrate创建相应数据表
seeders:种子文件,即初始化需要插入到数据库中的数据,运行sequelizedb:seed:all
创建Models和Migrations使用CLI工具Sequelize-CLI创建administrators表
sequelizemodel:create--nameadministrators--attributesid:integer,add_time:integer,last_login:integer,username:string,email:string,login_ip:string执行后会生成两个文件
/src/migrations/-create-administrators.js:创建数据表脚本,用于数据库初始化。
"usestrict";module.exports={ up:async(queryInterface,Sequelize)=>{ awaitqueryInterface.createTable("administrators",{ id:{ allowNull:false,autoIncrement:true,primaryKey:true,type:Sequelize.INTEGER,},add_time:{ type:Sequelize.INTEGER,},last_login:{ type:Sequelize.INTEGER,},username:{ type:Sequelize.STRING,},password:{ type:Sequelize.STRING,},email:{ type:Sequelize.STRING,},login_ip:{ type:Sequelize.STRING,},});},down:async(queryInterface,Sequelize)=>{ awaitqueryInterface.dropTable("administrators");},};/src/models/administrators.js:生成的model文件
"usestrict";const{ Model}=require("sequelize");module.exports=(sequelize,DataTypes)=>{ classadministratorsextendsModel{ }administrators.init({ id:{ type:DataTypes.INTEGER,autoIncrement:true,primaryKey:true,},add_time:DataTypes.INTEGER,last_login:DataTypes.INTEGER,username:DataTypes.STRING,password:DataTypes.STRING,email:DataTypes.STRING,login_ip:DataTypes.STRING,},{ sequelize,indexes:[{ unique:true,fields:["id"],},],freezeTableName:true,timestamps:false,//是否自动添加时间戳createAt,updateAtmodelName:"administrators",});returnadministrators;};现在执行命令:
sequelizedb:migrate执行成功后将在连接的数据库中创建数据表:administrators。
创建seedseed用于初始化插入数据,如管理员,在系统运行前需要创建一个默认账号,这些默认账号信息就写在seed文件中。
创建seed命令如下:
sequelizeseed:create--nameadministrator执行成功后将会在seeders文件夹中创建文件,修改代码如下:
npminstallsequelize--savenpminstallpgpg-hstore--save0现在将seed中的数据插入到数据库中,执行一下命令:
npminstallsequelize--savenpminstallpgpg-hstore--save1创建Services创建文件夹services,文件夹中代码封装与model交互的方法,包括所有CRUD(创建,读取,更新和删除)操作,创建administrators.js,实现的逻辑为获取账号信息、更新账号信息,代码如下:
npminstallsequelize--savenpminstallpgpg-hstore--save2创建Controllers上面创建的services文件用于控制器,在控制器文件夹中创建一个名为administrators.js的文件,代码如下:
npminstallsequelize--savenpminstallpgpg-hstore--save3创建Routers在文件夹routers中创建文件administrators.js文件,代码如下:
npminstallsequelize--savenpminstallpgpg-hstore--save4创建入口现在来为服务创建接口,项目根目录下创建文件app.js,代码如下:
npminstallsequelize--savenpminstallpgpg-hstore--save5现在执行命令nodeapp.js启动服务,将看到终端效果如下:
至此,完成一个基本的API登录服务,还有待完善,后续在迭代中完善。文章涉及的代码在GitHub上。
作者:天行无忌深入p-limit源码,如何限制并发数?
并发处理在现代编程中扮演着至关重要的角色,尤其在异步操作和并行任务处理中。虽然JavaScript是单线程执行的,但它通过Promise.all等API实现了并发效果,允许同时处理多个异步操作。
Promise.all是Promise库中的一个关键函数,它接受一个Promise数组作为参数。此函数会等待所有给定的Promise实例全部完成或其中一个失败,然后返回一个新Promise的数组结果。如果所有Promise都成功,则返回所有成功结果的数组;如果一个或多个Promise被拒绝,则返回第一个拒绝的Promise的reason。
然而,有时并发操作需要被限制。过多的并发请求可能给服务器带来压力,影响性能。这时候,p-limit库就显得尤为重要,它允许我们为并发操作设置一个上限。
p-limit提供了pLimit函数来定义并发限制。使用pLimit时,你可以传入一个数量参数,这个参数决定了同时可以执行的异步任务数量。函数返回一个新函数,该函数接收需要并发执行的异步任务。当执行队列中的任务数量达到上限时,新传入的任务会被加入队列,等待前面的任务释放资源后执行。
p-limit的实现中,核心在于初始化一个计数器和一个任务队列。队列采用了yocto-queue库实现,它提供了一个基于链表的队列结构。在并发处理过程中,p-limit通过enqueue函数将异步任务入队,并在队列中管理任务的执行顺序和限制。
enqueue函数负责将异步任务入队,同时对任务进行包装和控制,确保任务在队列中按顺序执行,且不会超过指定的并发限制。这通过使用async函数实现,以确保等待下一个微任务的到来,从而在异步更新的activeCount值上进行比较,以维持并发限制。
在实际执行时,每个任务的执行由run函数控制。此函数在内部管理并发计数,并在任务完成后执行下一个任务,确保并发限制被严格遵守。enqueue、run和next三个函数协同工作,构成了p-limit中一个动态、有限的异步任务执行流程。
此外,p-limit还包含了辅助函数用于管理任务状态,如获取当前执行任务数量(activeCount)、队列中等待任务数量(pendingCount)以及清空任务队列(clearQueue)。这些功能共同协作,确保并发处理既高效又可控。
通过p-limit库,开发人员能够轻松实现异步操作的并发控制,优化性能并防止服务器过载。了解其内部机制,能更好地利用并发处理技术,提升应用响应速度和用户体验。