Nodejs callback风格的原型方法promise化,this为undefined

Nodejs callback风格的原型方法promise化,this为undefined

在使用Q模块重构之前使用Async编写的代码时,在使用Q.denodeify()方法进行promise化并执行的时候提示方法内部的this为undefined, 并提示undefined 没有xxx方法。有没有遇到过类似的问题,或者是我用得不对,请大家支支招。

14 回复

在使用Q模块重构之前使用Async编写的代码时,在使用Q.denodeify()方法进行promise化并执行的时候提示方法内部的this为undefined, 并提示undefined 没有xxx方法。有没有遇到过类似的问题,或者是我用得不对,请大家支支招。


这还要代码?? 好吧!

        var client    =  oss.ossClient;
        var putObject = Q.denodeify(myPutObject);
	    function uploadOss(file) {
            var stream = fs.createReadStream(file.filePath);
            var option = {
                bucket: bucket,
                object: file.objectPath,
                srcFile: stream,
                contentType:file.contentType,
                contentLength: file.length
            };
            return putObject(option).then(function(result){
                fs.unlinkSync(file.filePath);
                req.oss = ossPath;
                console.log(result.statusCode);
                return next(null);
            }, function(error){
                fs.unlinkSync(file.filePath);
                return next(error);
            });
        }
        Q.all(fileList.map(uploadOss)).done();

因为oss内部调用了自身的request方法,这段代码运行的时候报错,undefined没有request方法。

当然可以。在Node.js中将传统的回调函数(callback-based)风格的方法转换为Promise风格时,经常会遇到this指向不正确的问题。这是因为当我们使用.denodeify()方法时,默认情况下它不会绑定到特定的对象实例上,因此this可能会变成undefined

示例问题

假设我们有一个类,其中包含一个需要转换为Promise风格的方法:

const Q = require('q');

class MyClass {
    constructor() {
        this.value = 'Hello';
    }

    doSomething(callback) {
        setTimeout(() => {
            callback(null, `${this.value} World`);
        }, 100);
    }
}

const instance = new MyClass();

// 使用Q.denodeify()进行promise化
const promisifiedDoSomething = Q.denodeify(instance.doSomething);

promisifiedDoSomething()
    .then(result => console.log(result))
    .catch(error => console.error(error));

上述代码运行时,可能会抛出错误,因为doSomething方法中的this可能为undefined,导致无法访问value属性。

解决方案

为了确保this能够正确地指向类的实例,我们需要手动绑定this。一种常见的方法是在创建Promise化版本的方法时,使用.bind()来绑定this

const Q = require('q');

class MyClass {
    constructor() {
        this.value = 'Hello';
    }

    doSomething(callback) {
        setTimeout(() => {
            callback(null, `${this.value} World`);
        }, 100);
    }
}

const instance = new MyClass();

// 使用.bind(this)来确保this正确绑定
const promisifiedDoSomething = Q.denodeify(instance.doSomething.bind(instance));

promisifiedDoSomething()
    .then(result => console.log(result)) // 输出 "Hello World"
    .catch(error => console.error(error));

解释

通过将instance.doSomething使用.bind(instance),我们确保了doSomething方法内部的this始终指向instance对象。这样,当doSomething方法被调用时,this.value就能正确地引用到类实例的value属性。

这种方法可以解决大多数情况下的thisundefined的问题,并使我们的代码更易于理解和维护。

自己把oss里面的putObject代码拿出来,显示的传了一个obj进行,这样就可以了,代码如下所示,但是这不合理呀,咋还要改别人的模块呢!

//自己封装的,代码来源oss-client/putObject

function myPutObject(obj, option, callback) {
    callback = callback || noop;
    var self = obj;
    if (typeof option.srcFile === 'string') {
        // upload by file path
        fs.stat(option.srcFile, function(err, state) {
            if (err) {
                return callback(err);
            }
            option.contentLength = state.size;
            //todo: add option.md5 = ...
            self.request('PUT', null, option, callback);
        });
    } else {
        // upload by buffer or stream
        self.request('PUT', null, option, callback);
    }
}

//新的调用方法

        var client    =  oss.ossClient;
        var putObject = Q.denodeify(myPutObject);
        function uploadOss(file) {
            var stream = fs.createReadStream(file.filePath);
            var option = {
                bucket: bucket,
                object: file.objectPath,
                srcFile: stream,
                contentType:file.contentType,
                contentLength: file.length
            };
        return putObject(client, option).then(function(result){
            fs.unlinkSync(file.filePath);
            req.oss = ossPath;
            console.log(result.statusCode);
            return next(null);
        }, function(error){
            fs.unlinkSync(file.filePath);
            return next(error);
        });
    }
    Q.all(fileList.map(uploadOss)).done();

bluebird

oss.putObjectAsync = Promise.promisify(oss.putObject);

同理denodeify

[@magicdawn](/user/magicdawn) 同理,存在我说的这个问题吗?

[@zhaomaoxin](/user/zhaomaoxin)

Q不了解,我的意思是把 denodeify得到的async方法赋给那个obj

好的,我先用bluebird写写看。

[@magicdawn](/user/magicdawn) 试了一下,同样的问题,你们就没有遇到过这样的问题吗?就是比如oss这个模块里面的方法putObject方法内部调用了this.request()这样的方法的时候报错,报undefined没有方法request,或者this被当作了全局对象global,这样报Object没有putObject这个方法就很正常了,就是promise化的时候对象转移了。

可以直接绑定到原来的对象上

[@left](/user/left) node 有可以直接使用的方法吗?fcall, fncall之类的吗?刚接触这个东西,玩不溜

q文档 https://github.com/kriskowal/q#adapting-node

var putObjectAsync = Q.nbind(oss.putObject,oss)

好像不是q的问题。应该是javascript的执行上下文搞混了。

function Test() {
  this.a = 1;
}

Test.prototype.get = function() { console.log(this.a); };

var T = new Test(); T.get();

var newTest = T.get; newTest();

var newTestB = T.get.bind(T); newTestB();

给putObject bind 上oss

在将Node.js中使用回调风格的原型方法Promise化时,this关键字可能会丢失上下文,导致其变为undefined。这通常发生在通过Function.prototype.bind()或直接调用方法时没有正确绑定上下文的情况下。

示例

假设我们有一个类MyClass,其中包含一个异步操作的方法doSomething

const Q = require('q');

class MyClass {
    constructor() {
        this.value = 'Hello World';
    }

    doSomething(callback) {
        setTimeout(() => {
            callback(null, this.value);
        }, 1000);
    }
}

const myInstance = new MyClass();

// 使用denodeify转换
const denodeifiedDoSomething = Q.denodeify(myInstance.doSomething);

// 调用转换后的函数
denodeifiedDoSomething()
    .then(value => console.log(value))
    .catch(error => console.error(error));

上面的代码会导致错误,因为setTimeout中的回调函数不会自动绑定this到实例。

解决方案

可以通过bind方法确保在异步调用中保留正确的上下文。以下是修复后的版本:

const Q = require('q');

class MyClass {
    constructor() {
        this.value = 'Hello World';
    }

    doSomething(callback) {
        setTimeout(() => {
            callback(null, this.value);
        }, 1000);
    }
}

const myInstance = new MyClass();

// 使用bind来确保doSomething内的this指向myInstance
const denodeifiedDoSomething = Q.denodeify(myInstance.doSomething.bind(myInstance));

// 调用
denodeifiedDoSomething()
    .then(value => console.log(value)) // 输出: Hello World
    .catch(error => console.error(error));

通过bind,我们将doSomething方法绑定到了myInstance上,这样在setTimeout的回调执行时,this依然会是myInstance,而不是undefined。这样就可以确保callback(null, this.value)中的this.value能正确访问到实例变量。

小结

使用Function.prototype.bind()可以帮助你正确地保留方法内部的上下文(即this)。在将回调函数转换为Promise时,这一点尤为重要,因为它确保了方法调用时的预期行为。

回到顶部