I was running a simple nodejs server, using nodejs url module to parse request.url.
After sending request to this server, the interpreter threw error in the place where a parsed query object embraced by ES6 ${} is supposed to be logged.
const http = require('http')
const url = require('url')
// let port = process.argv[2]
let port = 9000
const server = http.createServer()
server.listen(port)
server.on('request', (request, response) => {
let requestUrl = require('url').parse(request.url, true)
let path = requestUrl.path
let query = requestUrl.query
// console.log(`path: ${path}`)
// console.log('requestUrl: ',requestUrl)
console.log(`querytype: ${typeof query}`)
console.log(`query: ${JSON.stringify(query)}`)
// this will throw error: console.log(`query: ${query}`)
// this won't throw error : console.log(query)
response.statusCode = 200
response.end('')
})
It seems this is because query
has null
as a prototype, so it has not .toString()
method called by the template strings:
const obj = Object.create(null);
obj.foo = 'bar';
console.log(`${obj}`); // TypeError: Cannot convert object to primitive value
const obj = Object.create(null);
obj.foo = 'bar';
obj.toString = function () { return JSON.stringify(this); };
console.log(`${obj}`); // {"foo":"bar"}
This is correct behavior, passing an object to console.log
does not simply convert the object into a string, when you call console.log(someValue)
in Node console.log
will internally try to create a representation.
On the flip side when you use `${ someObject }`
JavaScript itself will attempt to convert it into a string using the Symbol.toPrimitive
method if it exists, otherwise it will attempt to call .toString
on the object.
Now if neither [Symbol.toPrimitive]
or .toString
exist then you'll get the TypeError: Cannot convert object to primitive value
. This might be surprising as `${ { x: 10 } }`
produces "[object Object]"
, but that's because Object.getPrototypeOf({ x: 10 }) === Object.prototype
which has a .toString
method.
The object in requestUrl.query
has null
prototype (Object.getPrototypeOf(requestUrl.query) === null
) hence it doesn't get the .toString
method like regular objects do. As such String(requestUrl.query)
/"" + requestUrl.query
/`${ requestUrl.query }`
all throw an error.
Perhaps a good course of action is to add a @@toPrimitive
to the dictionary object? I.e.:
this.query[Symbol.toPrimitive] = function() { return querystring.stringify(this); }
@vsemozhetbyt @Jamesernator @bnoordhuis
Thank you all for your information, this helped me understanding how ${}
works.
BTW if there's anyone else run into the same issue here's querystring source code
function parse(qs, sep, eq, options) {
const obj = Object.create(null);
...
return obj;
}
Most helpful comment
This is correct behavior, passing an object to
console.log
does not simply convert the object into a string, when you callconsole.log(someValue)
in Nodeconsole.log
will internally try to create a representation.On the flip side when you use
`${ someObject }`
JavaScript itself will attempt to convert it into a string using theSymbol.toPrimitive
method if it exists, otherwise it will attempt to call.toString
on the object.Now if neither
[Symbol.toPrimitive]
or.toString
exist then you'll get theTypeError: Cannot convert object to primitive value
. This might be surprising as`${ { x: 10 } }`
produces"[object Object]"
, but that's becauseObject.getPrototypeOf({ x: 10 }) === Object.prototype
which has a.toString
method.The object in
requestUrl.query
hasnull
prototype (Object.getPrototypeOf(requestUrl.query) === null
) hence it doesn't get the.toString
method like regular objects do. As suchString(requestUrl.query)
/"" + requestUrl.query
/`${ requestUrl.query }`
all throw an error.