Quantcast
Channel: CNode:Node.js专业中文社区
Viewing all 14821 articles
Browse latest View live

谁知道typescript输出时怎么保留目录结构?

$
0
0

我希望我的src目录存放ts源码,然后将js文件输出到单独的目录,tsconfig里我设置了outDir和include:[“src/*/”],可是我在src子目录里创建的ts文件输入tsc编译时还是在根目录输出文件,不喜欢那种把ts和js放到同目录下的方式,代码结构太乱,IDE虽然看着还行把js当做下级,但还是觉得结构乱。我觉得现在js项目结构真的乱的要死,babel,eslint,package.json等等各种配置文件全在root目录里,我虽然非常讨厌java那种啰嗦的不能再啰嗦的语法,但项目结构真心给个赞,主目录一个pom文件和src目录。闲话少说,谁知道上面问题怎么解决,我原以为会输出目录结构的,结果tsc后并没有

来自酷炫的 CNodeMD


node项目有没有类似于像rails console那样的控制台

$
0
0

node项目有没有类似于像rails的rails console那样的控制台可以用

[杭州滨江] [25K+ ] TELETRAAN 诚邀优秀前端工程师加入,海归团队|弹性工作|待遇优厚|氛围超赞,欢迎上车!

$
0
0

关于 [ TELETRAAN ]

我们是去年从硅谷回国的创业团队,主要面向 B 端客户,为其开发工业物联网应用平台。 通过数据分析与挖掘、机器学习等前沿技术,更为便捷、迅速地为客户提供企业级应用的设计、开发及部署服务。

公司网站: https://teletraan.io


招聘职位 [ 资深前端工程师] 25K+

职位描述

  • 负责企业级Web应用(SaaS)的界面开发,注重于网页应用的交互性,可靠性和可扩展性
  • 在设计和实施应用程序的外观和工作方面发挥主导作用的同时,还将与后端团队和数据平台团队紧密合作
  • 负责技术选型,设计框架,带领前端工程师团队从0开始实现一个完整复杂的应用开发(前端部分)

任职要求

  • 3年网页前端开发经验,能独立搭建一个新应用产品的框架
  • 熟练掌握HTML和CSS语法和布局
  • 熟练掌握JavaScript和JQuery
  • 熟悉至少一个前端框架,比如ReactJS, AngularJS等,并有相应的实战经验
  • 熟练掌握异步请求处理(AJAX)和部分页面更新的技术
  • 用户界面(UI)设计和开发的强大背景
  • 通过Restful API与后端集成的实践经验
  • 熟悉各种前端页面优化的方法
  • 积极理解客户的需求,同时快速的生成新的设计方案
  • 有强烈的上进心和求知欲,善于学习和运用新知识
  • 善于沟通和逻辑表达,有强烈的团队意识和执行力

加分选项

  • 985,211院校毕业生
  • 对后端架构有一定了解
  • 有设计企业资源分析工具或可视化工具的工作经验
  • TDD或BDD的单元测试经验
  • 对浏览器兼容性的全面了解

办公地点:杭州滨江区江陵路地铁站附近


在 TELETRAAN,工作节奏快速、流程极简、高效并追求卓越。
如果你热爱技术,有极强的学习能力,
乐于沟通和分享,敢于创新与实践,
不要犹豫,快来加入我们!
请发送简历至 job.cn@teletraan.io
邮件标题:V2EX- 应聘职位-你的名字
Teletraan 邀请你与我们一起去探索人生更多的可能性!

我们能提供:

MAC 工作环境,全正版软件

  • 硅谷管理模式,弹性工作,不打卡
  • 高效工作,拒绝无谓加班
  • 扁平化管理,CXO 与大家同在一个开放式办公区
  • 有很多机会实践最新的前沿技术,和去硅谷交流学习
  • 五险一金,带薪年假;绩效奖金,各种股权激励
  • 水果、零食、饮料无限供应

PS: 其他职位也招,欢迎打扰!
招聘主页:https://hr.lagou.com/company/gongsi/j211574.html
公司微信:Teletraan (17767179603)

egg 如何查看线上服务的运行状态?

$
0
0

类似 pm2 lpm2 logs的功能

qrcode-parser 纯 JavaScript 识别图片二维码的内容

关于nodejs ftp上传文件 如何用socksv5代理上传

$
0
0

问题是这样的,现在需要把文件通过ftp上传文件,然后我使用的这个库 jsftp但是按照示例直接连接连接不上,然后又说用 socksv5代理上传,这个是什么意思?

const Ftp = new jsftp({
        host: "ip",
        port: 21, // defaults to 21
        user: "user", // defaults to "anonymous"
        pass: "passw", // defaults to "@anonymous"
        createSocket: ({ port, host }, firstAction) => {
            return SocksClient.createConnection({
                proxy: {
                    ipaddress: 'ip',
                    port: 8833,
                    type: 5
                },

                command: 'connect',

                destination: {
                    host,
                    port
                }
            })
        }

这样连接还是连接不上 求社区大神指教指教 谢谢

请教个问题,关于JS作用域的,看了网上的也不是很懂。

$
0
0

V{77V{BPQCQV5M{N6{075XM.png如图,我把 var temp = {};写在for循环外面之后 alldata里面的数全部变成了for循环后一个temp,但是写在里面就是我想要的结果没什么呀 ![E7}KSXHF5APU%}BQ}3QQOD.png想问一下js 的array.push()不是异步回调来的把?

区块链开发、以太坊开发的技术资料资源汇总

$
0
0

一个适合区块链新手的以太坊DApp开发教程: http://xc.hubwiz.com/course/5a952991adb3847553d205d1

一些免费区块链、以太坊技术开发相关的文件,有需要的可以下载:

  1. web3.js API官方文档中文版:https://pan.baidu.com/s/1hOV9hEzi7hFxJCL4LTvC6g
  2. 以太坊官方文档中文版 :https://pan.baidu.com/s/1ktODJKLMBmkOsi8MPrpIJA
  3. 以太坊白皮书中文版 :https://pan.baidu.com/s/1bzAFnzJ35hlQxJ2J4Oj-Ow
  4. Solidity的官方文档中文版 :https://pan.baidu.com/s/18yp9XjEqAHpiFm2ZSCygHw
  5. Truffle的官方文档中文版 :https://pan.baidu.com/s/1y6SVd7lSLUHK21YF5FzIUQ
  6. C#区块链编程指南 :https://pan.baidu.com/s/1sJPLqp1eQqkG7jmxqwn3EA
  7. 区块链技术指南 :https://pan.baidu.com/s/13cJxAa80I6iMCczA04CZhg
  8. 精通比特币中文版 :https://pan.baidu.com/s/1lz6te3wcQuNJm28rFvBfxg
  9. Node.js区块链开发 :https://pan.baidu.com/s/1Ldpn0DvJ5LgLqwix6eWgyg
  10. geth使用指南文档中文版 :https://pan.baidu.com/s/1M0WxhmumF_fRqzt_cegnag
  11. 以太坊DApp开发环境搭建-Ubuntu : https://pan.baidu.com/s/10qL4q-uKooMehv9X2R1qSA
  12. 以太坊DApp开发环境搭建-windows :https://pan.baidu.com/s/1cyYkhIJIFuI2oyxM9Ut0eA

初学node,想请教一下express中根据不同url给不同请求,但是不存在的请求如何处理

$
0
0

image.png我用的router,但是不知道不存在的router请求如何处理,例如 image.png可以有内容,但是我的路径后边随便加一个不存在路径参数如何给一个404页面 image.png是通过路由控制吗?

从头实现一个koa框架

$
0
0

koajs是最流行的nodejs后端框架之一,有很多网站都使用koa进行开发,同时社区也涌现出了一大批基于koa封装的企业级框架。然而,在这些亮眼的成绩背后,作为核心引擎的koa代码库本身,却非常的精简,不得不让人惊叹于其巧妙的设计。

在平时的工作开发中,笔者是koa的重度用户,因此对其背后的原理自然也是非常感兴趣,因此在闲暇之余进行了研究。不过本篇文章,并不是源码分析,而是从相反的角度,向大家展示如何从头开发实现一个koa框架,在这个过程中,koa中最重要的几个概念和原理都会得到展现。相信大家在看完本文之后,会对koa有一个更深入的理解,同时在阅读本文之后再去阅读koa源码,思路也将非常的顺畅。

首先放出笔者实现的这个koa框架代码库地址:simpleKoa

需要说明的是,本文实现的koa是koa 2版本,也就是基于async/await的,因此需要node版本在7.6以上。如果读者的node版本较低,建议升级,或者安装babel-cli,利用其中的babel-node来运行例子。

四条主线

笔者认为,理解koa,主要需要搞懂四条主线,其实也是实现koa的四个步骤,分别是

  1. 封装node http Server
  2. 构造resquest, response, context对象
  3. 中间件机制
  4. 错误处理

下面就一一进行分析。

主线一:封装node http Server: 从hello world说起

首先,不考虑框架,如果使用原生http模块来实现一个返回hello world的后端app,代码如下:

let http = require('http');

let server = http.createServer((req, res) => {
    res.writeHead(200);
    res.end('hello world');
});

server.listen(3000, () => {
    console.log('listenning on 3000');
});

实现koa的第一步,就是对这个原生的过程进行封装,为此,我们首先创建application.js实现一个Application对象:

// application.js
let http = require('http');

class Application {

    /**
     * 构造函数
     */
    constructor() {
        this.callbackFunc;
    }

    /**
     * 开启http server并传入callback
     */
    listen(...args) {
        let server = http.createServer(this.callback());
        server.listen(...args);
    }

    /**
     * 挂载回调函数
     * @param {Function} fn 回调处理函数
     */
    use(fn) {
        this.callbackFunc = fn;
    }

    /**
     * 获取http server所需的callback函数
     * @return {Function} fn
     */
    callback() {
        return (req, res) => {
            this.callbackFunc(req, res);
        };
    }

}

module.exports = Application;

然后创建example.js:

let simpleKoa = require('./application');
let app = new simpleKoa();

app.use((req, res) => {
    res.writeHead(200);
    res.end('hello world');
});

app.listen(3000, () => {
    console.log('listening on 3000');
});

可以看到,我们已经初步完成了对于http server的封装,主要实现了app.use注册回调函数,app.listen语法糖开启server并传入回调函数了,典型的koa风格。

但是美中不足的是,我们传入的回调函数,参数依然使用的是reqres,也就是node原生的request和response对象,这些原生对象和api提供的方法不够便捷,不符合一个框架需要提供的易用性。因此,我们需要进入第二条主线了。

主线二:构造request, response, context对象

如果阅读koa文档,会发现koa有三个重要的对象,分别是request, response, context。其中request是对node原生的request的封装,response是对node原生response对象的封装,context对象则是回调函数上下文对象,挂载了koa request和response对象。下面我们一一来说明。

首先要明确的是,对于koa的request和response对象,只是提供了对node原生request和response对象的一些方法的封装,明确了这一点,我们的思路是,使用js的getter和setter属性,基于node的对象req/res对象封装koa的request/response对象。

规划一下我们要封装哪些易用的方法。这里在文章中为了易懂,姑且只实现以下方法:

对于simpleKoa request对象,实现query读取方法,能够读取到url中的参数,返回一个对象。

对于simpleKoa response对象,实现status读写方法,分别是读取和设置http response的状态码,以及body方法,用于构造返回信息。

而simpleKoa context对象,则挂载了request和response对象,并对一些常用方法进行了代理。

首先创建request.js:

// request.js
let url = require('url');

module.exports = {

    get query() {
        return url.parse(this.req.url, true).query;
    }

};

很简单,就是导出了一个对象,其中包含了一个query的读取方法,通过url.parse方法解析url中的参数,并以对象的形式返回。需要注意的是,代码中的this.req代表的是node的原生request对象,this.req.url就是node原生request中获取url的方法。稍后我们修改application.js的时候,会为koa的request对象挂载这个req。

然后创建response.js:

// response.js
module.exports = {

    get body() {
        return this._body;
    },

    /**
     * 设置返回给客户端的body内容
     *
     * @param {mixed} data body内容
     */
    set body(data) {
        this._body = data;
    },

    get status() {
        return this.res.statusCode;
    },

    /**
     * 设置返回给客户端的stausCode
     *
     * @param {number} statusCode 状态码
     */
    set status(statusCode) {
        if (typeof statusCode !== 'number') {
            throw new Error('statusCode must be a number!');
        }
        this.res.statusCode = statusCode;
    }

};

也很简单。status读写方法分别设置或读取this.res.statusCode。同样的,这个this.res是挂载的node原生response对象。而body读写方法分别设置、读取一个名为this._body的属性。这里设置body的时候并没有直接调用this.res.end来返回信息,这是考虑到koa当中我们可能会多次调用response的body方法覆盖性设置数据。真正的返回消息操作会在application.js中存在。

然后我们创建context.js文件,构造context对象的原型:

// context.js
module.exports = {

    get query() {
        return this.request.query;
    },

    get body() {
        return this.response.body;
    },

    set body(data) {
        this.response.body = data;
    },

    get status() {
        return this.response.status;
    },

    set status(statusCode) {
        this.response.status = statusCode;
    }

};

可以看到主要是做一些常用方法的代理,通过context.query直接代理了context.request.querycontext.bodycontext.status代理了context.response.bodycontext.response.status。而context.requestcontext.response则会在application.js中挂载。

由于context对象定义比较简单并且规范,当实现更多代理方法时候,这样一个一个通过声明的方式显然有点笨,js中,设置setter/getter,可以通过对象的__defineSetter____defineSetter__来实现。为此,我们精简了上面的context.js实现方法,精简版本如下:

let proto = {};

// 为proto名为property的属性设置setter
function delegateSet(property, name) {
    proto.__defineSetter__(name, function (val) {
        this[property][name] = val;
    });
}

// 为proto名为property的属性设置getter
function delegateGet(property, name) {
    proto.__defineGetter__(name, function () {
        return this[property][name];
    });
}

// 定义request中要代理的setter和getter
let requestSet = [];
let requestGet = ['query'];

// 定义response中要代理的setter和getter
let responseSet = ['body', 'status'];
let responseGet = responseSet;

requestSet.forEach(ele => {
    delegateSet('request', ele);
});

requestGet.forEach(ele => {
    delegateGet('request', ele);
});

responseSet.forEach(ele => {
    delegateSet('response', ele);
});

responseGet.forEach(ele => {
    delegateGet('response', ele);
});

module.exports = proto;

这样,当我们希望代理更多request和response方法的时候,可以直接向requestGet/requestSet/responseGet/responseSet数组中添加method的名称即可(前提是在request和response中实现了)。

最后让我们来修改application.js,基于刚才的3个对象原型来创建request, response, context对象:

// application.js
let http = require('http');
let context = require('./context');
let request = require('./request');
let response = require('./response');

class Application {

    /**
     * 构造函数
     */
    constructor() {
        this.callbackFunc;
        this.context = context;
        this.request = request;
        this.response = response;
    }

    /**
     * 开启http server并传入callback
     */
    listen(...args) {
        let server = http.createServer(this.callback());
        server.listen(...args);
    }

    /**
     * 挂载回调函数
     * @param {Function} fn 回调处理函数
     */
    use(fn) {
        this.callbackFunc = fn;
    }

    /**
     * 获取http server所需的callback函数
     * @return {Function} fn
     */
    callback() {
        return (req, res) => {
            let ctx = this.createContext(req, res);
            let respond = () => this.responseBody(ctx);
            this.callbackFunc(ctx).then(respond);
        };
    }

    /**
     * 构造ctx
     * @param {Object} req node req实例
     * @param {Object} res node res实例
     * @return {Object} ctx实例
     */
    createContext(req, res) {
        // 针对每个请求,都要创建ctx对象
        let ctx = Object.create(this.context);
        ctx.request = Object.create(this.request);
        ctx.response = Object.create(this.response);
        ctx.req = ctx.request.req = req;
        ctx.res = ctx.response.res = res;
        return ctx;
    }

    /**
     * 对客户端消息进行回复
     * @param {Object} ctx ctx实例
     */
    responseBody(ctx) {
        let content = ctx.body;
        if (typeof content === 'string') {
            ctx.res.end(content);
        }
        else if (typeof content === 'object') {
            ctx.res.end(JSON.stringify(content));
        }
    }

}

可以看到,最主要的是增加了createContext方法,基于我们之前创建的context为原型,使用Object.create(this.context)方法创建了ctx,并同样通过Object.create(this.request)Object.create(this.response)创建了request/response对象并挂在到了ctx对象上面。此外,还将原生node的req/res对象挂载到了ctx.request.req/ctx.reqctx.response.res/ctx.res对象上。

回过头去看我们之前的context/request/response.js文件,就能知道当时使用的this.res或者this.response之类的是从哪里来的了,原来是在这个createContext方法中挂载到了对应的实例上。一张图来说明其中的关系:

构建了运行时上下文ctx之后,我们的app.use回调函数参数就都基于ctx了。

下面一张图描述了ctx对象的结构和继承关系: image.png

最后回忆我们的ctx.body方法,并没有直接返回消息体,而是将消息存储在了一个变量属性中。为了每次回调函数处理结束之后返回消息,我们创建了responseBody方法,主要作用就是通过ctx.body读取存储的消息,然后调用ctx.res.end返回消息并关闭连接。从方法中知道,我们的body消息体可以是字符串,也可以是对象(会序列化为字符串返回)。注意这个方法的调用是在回调函数结束之后调用的,而我们的回调函数是一个async函数,其执行结束后会返回一个Promise对象,因此我们只需要在其后通过.then方法调用我们的responseBody即可,这就是this.callbackFunc(ctx).then(respond)的意义。

然后我们来测试一下目前为止的框架。修改example.js如下:

let simpleKoa = require('./application');
let app = new simpleKoa();

app.use(async ctx => {
    ctx.body = 'hello ' + ctx.query.name;
});

app.listen(3000, () => {
    console.log('listening on 3000');
});

可以看到这个时候我们通过app.use传入的已经不再是原生的function (req, res)回调函数,而是koa2中的async函数,接收ctx作为参数。为了测试,在浏览器访问localhost:3000?name=tom,可以看到返回了’hello tom’,符合预期。

这里再插入分析一个知识概念。从刚才的实现中,我们知道了this.context是我们的中间件中上下文ctx对象的原型。因此在实际开发中,我们可以将一些常用的方法挂载到this.context上面,这样,在中间件ctx中,我们也可以方便的使用这些方法了,这个概念就叫做ctx的扩展,一个例子是阿里的egg.js框架已经把这个扩展机制作为一部分,融入到了框架开发中。

下面就展示一个例子,我们写一个echoData的方法作为扩展,传入errno, data, errmsg,能够给客户端返回结构化的消息结果:

let SimpleKoa = require('./application');
let app = new SimpleKoa();

// 对ctx进行扩展
app.context.echoData = function (errno = 0, data = null, errmsg = '') {
    this.res.setHeader('Content-Type', 'application/json;charset=utf-8');
    this.body = {
        errno: errno,
        data: data,
        errmsg: errmsg
    };
};

app.use(async ctx => {
    let data = {
        name: 'tom',
        age: 16,
        sex: 'male'
    }
    // 这里使用扩展,方便的返回utf-8格式编码,带有errno和errmsg的消息体
    ctx.echoData(0, data, 'success');
});

app.listen(3000, () => {
    console.log('listenning on 3000');
});

主线三:中间件机制

到目前为止,我们成功封装了http server,并构造了context, request, response对象。但最重要的一条主线却还没有实现,那就是koa的中间件机制。

关于koa的中间件洋葱执行模型,koa 1中使用的是generator + co.js执行的方式,koa 2中则使用了async/await。关于koa 1中的中间件原理,我曾写过一篇文章进行解释,请移步:深入探析koa之中间件流程控制篇

这里我们实现的是基于koa 2的,因此再描述一下原理。为了便于理解,假设我们有3个async函数:

async function m1(next) {
    console.log('m1');
    await next();
}

async function m2(next) {
    console.log('m2');
    await next();
}

async function m3() {
    console.log('m3');
}

我们希望能够构造出一个函数,实现的效果是让三个函数依次执行。首先考虑想让m2执行完毕后,await next()去执行m3函数,那么显然,需要构造一个next函数,作用是调用m3,然后作为参数传给m2

let next1 = async function () {
    await m3();
}

m2(next1);

// 输出:m2,m3

进一步,考虑从m1开始执行,那么,m1的next参数需要是一个执行m2的函数,并且给m2传入的参数是m3,下面来模拟:

let next1 = async function () {
    await m3();
}

let next2 = async function () {
    await m2(next1);
}

m1(next2);

// 输出:m1,m2,m3

那么对于n个async函数,希望他们按顺序依次执行呢?可以看到,产生nextn的过程能够抽象为一个函数:

function createNext(middleware, oldNext) {
    return async function () {
        await middleware(oldNext);
    }
}

let next1 = createNext(m3, null);
let next2 = createNext(m2, next1);
let next3 = createNext(m1, next2);

next3();

// 输出m1, m2, m3

进一步精简:

let middlewares = [m1, m2, m3];
let len = middlewares.length;

// 最后一个中间件的next设置为一个立即resolve的promise函数
let next = async function () {
    return Promise.resolve();
}
for (let i = len - 1; i >= 0; i--) {
    next = createNext(middlewares[i], next);
}

next();

// 输出m1, m2, m3

至此,我们也有了koa中间件机制实现的思路,新的application.js如下:

/**
 * @file simpleKoa application对象
 */
let http = require('http');
let context = require('./context');
let request = require('./request');
let response = require('.//response');

class Application {

    /**
     * 构造函数
     */
    constructor() {
        this.middlewares = [];
        this.context = context;
        this.request = request;
        this.response = response;
    }

    // ...省略中间 

    /**
     * 中间件挂载
     * @param {Function} middleware 中间件函数
     */
    use(middleware) {
        this.middlewares.push(middleware);
    }

    /**
     * 中间件合并方法,将中间件数组合并为一个中间件
     * @return {Function}
     */
    compose() {
        // 将middlewares合并为一个函数,该函数接收一个ctx对象
        return async ctx => {

            function createNext(middleware, oldNext) {
                return async () => {
                    await middleware(ctx, oldNext);
                }
            }

            let len = this.middlewares.length;
            let next = async () => {
                return Promise.resolve();
            };
            for (let i = len - 1; i >= 0; i--) {
                let currentMiddleware = this.middlewares[i];
                next = createNext(currentMiddleware, next);
            }

            await next();
        };
    }

    /**
     * 获取http server所需的callback函数
     * @return {Function} fn
     */
    callback() {
        return (req, res) => {
            let ctx = this.createContext(req, res);
            let respond = () => this.responseBody(ctx);
            let fn = this.compose();
            return fn(ctx).then(respond);
        };
    }

    // ...省略后面 

}

module.exports = Application;


可以看到,首先对app.use进行改造了,每次调用app.use,就向this.middlewares中push一个回调函数。然后增加了一个compose()方法,利用我们前文分析的原理,对middlewares数组中的函数进行组装,返回一个最终的函数。最后,在callback()方法中,调用compose()得到最终回调函数,并执行。

改写example.js验证一下中间件机制:

let simpleKoa = require('./application');
let app = new simpleKoa();

let responseData = {};

app.use(async (ctx, next) => {
    responseData.name = 'tom';
    await next();
    ctx.body = responseData;
});

app.use(async (ctx, next) => {
    responseData.age = 16;
    await next();
});

app.use(async ctx => {
    responseData.sex = 'male';
});

app.listen(3000, () => {
    console.log('listening on 3000');
});

// 返回{ name: "tom", age: 16, sex: "male"}

例子中一共三个中间件,分别对responseData增加了name, age, sex属性,最后返回该数据。

至此,一个koa框架基本已经浮出水面了,不过我们还需要进行最后一个主线的分析:错误处理。

主线四:错误处理

一个健壮的框架,必须保证在发生错误的时候,能够捕获错误并有降级方案返回给客户端。但显然现在我们的框架还做不到这一点,假设我们修改一下例子,我们的中间件中,有一个发生错误抛出了异常:

let simpleKoa = require('./application');
let app = new simpleKoa();

let responseData = {};
app.use(async (ctx, next) => {
    responseData.name = 'tom';
    await next();
    ctx.body = responseData;
});

app.use(async (ctx, next) => {
    responseData.age = 16;
    await next();
});

app.use(async ctx => {
    responseData.sex = 'male';
    // 这里发生了错误,抛出了异常
    throw new Error('oooops');
});

app.listen(3000, () => {
    console.log('listening on 3000');
});

这个时候访问浏览器,是得不到任何响应的,这是因为异常并没有被我们的框架捕获并进行降级处理。回顾我们application.js中的中间件执行代码:

// application.js
// ...
    callback() {
        return (req, res) => {
            let ctx = this.createContext(req, res);
            let respond = () => this.responseBody(ctx);
            let fn = this.compose();
            return fn(ctx).then(respond);
        };
    }
// ...

其中我们知道,fn是一个async函数,执行后返回一个promise,回想promise的错误处理是怎样的?没错,我们只需要定义一个onerror函数,里面进行错误发生时候的降级处理,然后在promise的catch方法中引用这个函数即可。

于此同时,回顾koa框架,我们知道在错误发生的时候,app对象可以通过app.on('error', callback)订阅错误事件,这有助于我们几种处理错误,比如打印日志之类的操作。为此,我们也要对Application对象进行改造,让其继承nodejs中的events对象,然后在onerror方法中emit错误事件。改造后的application.js如下:

/**
 * @file simpleKoa application对象
 */

let EventEmitter = require('events');
let http = require('http');
let context = require('./context');
let request = require('./request');
let response = require('./response');

class Application extends EventEmitter {

    /**
     * 构造函数
     */
    constructor() {
        super();
        this.middlewares = [];
        this.context = context;
        this.request = request;
        this.response = response;
    }

    // ...

    /**
     * 获取http server所需的callback函数
     * @return {Function} fn
     */
    callback() {
        return (req, res) => {
            let ctx = this.createContext(req, res);
            let respond = () => this.responseBody(ctx);
            let onerror = (err) => this.onerror(err, ctx);
            let fn = this.compose();
            // 在这里catch异常,调用onerror方法处理异常
            return fn(ctx).then(respond).catch(onerror);
        };
    }

    // ... 

    /**
     * 错误处理
     * @param {Object} err Error对象
     * @param {Object} ctx ctx实例
     */
    onerror(err, ctx) {
        if (err.code === 'ENOENT') {
            ctx.status = 404;
        }
        else {
            ctx.status = 500;
        }
        let msg = err.message || 'Internal error';
        ctx.res.end(msg);
        // 触发error事件
        this.emit('error', err);
    }

}

module.exports = Application;

可以看到,onerror方法的对异常的处理主要是获取异常状态码,当err.code为’ENOENT’的时候,返回的消息头设置为404,否则默认设置为500,然后消息体设置为err.message,如果异常中message属性为空,则默认消息体设置为’Internal error’。此后调用ctx.res.end返回消息,这样就能保证即使异常情况下,客户端也能收到返回值。最后通过this.emit出发error事件。

然后我们写一个example来验证错误处理:

let simpleKoa = require('./application');
let app = new simpleKoa();

app.use(async ctx => {
    throw new Error('ooops');
});

app.on('error', (err) => {
    console.log(err.stack);
});

app.listen(3000, () => {
    console.log('listening on 3000');
});

浏览器访问’localhost:3000’的时候,得到返回’ooops’,同时http状态码为500 。同时app.on(‘error’)订阅到了异常事件,在回调函数中打印出了错误栈信息。

关于错误处理,这里多说一点。虽然koa中内置了错误处理机制,但是实际业务开发中,我们往往希望能够自定义错误处理方式,这个时候,比较好的办法是在最开头增加一个错误捕获中间件,然后根据错误进行定制化的处理,比如:

// 错误处理中间件
app.use(async (ctx, next) => {
    try {
        await next();
    }
    catch (err) {
        // 在这里进行定制化的错误处理
    }
});
// ...其他中间件

至此,我们就完整实现了一个轻量版的koa框架。

结语

完整的simpleKoa代码库地址为:simpleKoa,里面还附带了一些example。

理解了这个轻量版koa的实现原理,读者还可以去看看koa的源码,会发现机制和我们实现的框架是非常类似的,无非是多了一些细节,比如说,完整koa的context/request/response方法上面挂载了更多好用的method,或者很多方法中容错处理更好等等。具体在本文中就不展开讲了,留给感兴趣的读者去探索吧~。

记录一次神奇的bug——dom的id有时也会坑人

$
0
0

使用tinymce的编辑器时,按照正常方式配置完毕后打开页面报了一大堆404
image.png
根据经验,我判断应该是有个baseURL之类的选项没有写,导致tinymce默认从根路径加载资源,不过即便如此也不应该出现两个/才对
于是我从官网上找了所有和url相关的配置尝试修改,但是404信息上显示的请求地址没有任何的变化

走投无路只能打断点看源码,经过一步步追踪之后,终于定位到了发生问题的位置

			self.baseURL = new URI(documentBaseURL).toAbsolute(baseURL);

documentBaseURL是根路径,而baseURL是从根路径开始计算的相对路径,测试发现这里baseURL为undefined,于是向上寻找baseURL的声明与赋值位置

// If tinymce is defined and has a base use that or use the old tinyMCEPreInit
			preInit = window.tinymce || window.tinyMCEPreInit;
			if (preInit) {
				baseURL = preInit.base || preInit.baseURL;
				suffix = preInit.suffix;
			} else {
				// Get base where the tinymce script is located
// ...

可以看出tinymce会将一个名为tinymce对象导出到window,里面包含了baseURL
在已经加载过tinymce的情况下(在源码里以window.tinymce是否存在来判断),新加载的tinymce会使用旧的tinymce的baseURL
没有加载过tinymce的情况下,会查找tinymce的script标签,根据标签的src计算baseURL
显然我之前没有加载过tinymce,但是代码竟然进入了if里而不是else里,也就是说preInit被定义过了
console.log一下,破案了 image.png可能是出于方便开发者调试的原因,很多浏览器都会自动为有id的元素创建一个变量window[dom.id] = dom,而我习惯性地给tinymce容器起了tinymce这个id,于是tinymce初始化的时候访问window.tinymce就会拿到这个元素并错误的把它当成了初始化过的tinymce创建的对象,然后从这个对象上获取baseURL值(undefined)而且没有做检查,结果就得到了错误的baseURL导致了这个bug
修改了tinymce容器的id,果然正常的加载了编辑器。。。

node-archiver 压缩包的中文文件名变乱码

$
0
0

在 koa 中使用 node-archiver 模块进行打包压缩

const zip = archiver('zip');
zip.pipe(fs.createWriteStream('1.zip'));

const file = '1 - 副本.log';
zip.append(fs.createReadStream(file), {
    // 命名中含有中文
    name: file 
})
await zip.finalize();

在服务器端打包压缩,提供前端下载,文件名含有中文的话,下载下来解压,文件名乱码(需求是必须保留原文件名):

TIM截图20180328171020.png

请问有没有大佬知道如何解决?

网站显示“不安全”是什么问题呀?大佬们帮帮忙

[咨询] 大家都用什么软件来管理api文档呢?rap2,swagger,postman?

$
0
0

1.大家都用什么软件来管理api文档呢?rap2,swagger,postman? 2.用rap2时你们是这样建目录的不(V1,V2…代表版本,就是按版本来划分接口): image.png 3.如果不是接口 ,只是简单的说明你们一般放在哪里呢? 比如 V3版本,融云推送新增了一个字段from_star_user,像这样说明你们会放在哪里呢?rap2可以?

nextTick中为什么需要async_hooks

$
0
0

最近被一个地方搞得很头疼,就是nextTick中为什么需要async_hooks:

// ./lib/internal/process/next_tick.js
function _tickCallback() {
    let tock;
    do {
      while (tock = shift()) {
        const asyncId = tock[async_id_symbol];
        emitBefore(asyncId, tock[trigger_async_id_symbol]);
        if (destroyHooksExist())
          emitDestroy(asyncId);
        const callback = tock.callback;
        if (tock.args === undefined)
          callback();
        else
          Reflect.apply(callback, undefined, tock.args);
        emitAfter(asyncId);
      }
      runMicrotasks();
    } while (head.top !== head.bottom || emitPromiseRejectionWarnings());
    tickInfo[kHasPromiseRejections] = 0;
  }

之前做过nextTick的源码分析,当时并没有去深入探究。最近抽时间在async_hook的文档中找到了答案:

The TCPSERVERWRAP is not part of this graph, even though it was the reason for console.log() being called. This is because binding to a port without a hostname is a synchronous operation, but to maintain a completely asynchronous API the user’s callback is placed in a process.nextTick().

于是下决心把async_hook的文档翻译的一遍,还是有不少收获的。如果大家有兴趣的话,可以读一读,链接在此。有问题的地方可以直接提pr,我会尽快处理,如果对文档有异议却又不能确定,请在这个issue下提出。


寻前端动画开发以及UE工程师兼职

$
0
0

需要兼职人员

  1. 前端开发动画,要求对css兼容性有一定了解,动画有一定经验
  2. UE工程师

使用sequelize的时候,mysql自增长Id总是 Duplicate entry '0' for key 'PRIMARY错误

$
0
0

生成的语句是:

INSERT INTO `apps` (`id`,`name`,`uid`,`created_at`,`updated_at`) VALUES (DEFAULT,\'yahue\',1,\'2018-03-28 12:22:20\',\'2018-03-28 12:22:20\');

sequelize自动增长id为 DEFAULT还是会有 Duplicate entry ‘0’ for key 'PRIMARY 错误 我是要设置 mysql的什么属性吗? 有哪位大神遇到过这个问题?

东方闪电261462d3-826e-4d47-b58d-23e4af283ab8261462d3-826e-4d47-b58d-23e4af283ab8

99元一年的279元3年阿里云服务器 团购

阿里云福利-99/年 279/3年。快上车

Viewing all 14821 articles
Browse latest View live