I am making a small token project with front-end website to ease the use. The architecture is Ethereum private network with ERC20 contract, PostgreSQL to associate address with alias of wallet, NodeJS and AngularJS.
I am very new to Javascript Promises and I struggled a lot in the past few months to understand how to use them "properly".
What follows is the code for my ExpressJS endpoints. It is working, but could you tell me whether I am doing things properly?
Another question: I would like to move the initialisation of sequelize, web3 and truffle on another js file. I tried with require() but variables become undefined; what should I do?
const express = require('express');
const path = require('path');
const Web3 = require('web3');
const fs = require('fs');
const TruffleContract = require('truffle-contract');
const Sequelize = require('sequelize');
const Aigle = require('aigle');
const router = express.Router();
const Op = Sequelize.Op;
const sequelize = new Sequelize('aircoin', 'giuseppe', '12345', {
dialect: 'postgres',
host: "localhost",
port: 5432,
operatorsAliases: false
});
let web3;
let contract;
if (typeof web3 !== 'undefined') {
web3Provider = web3.currentProvider;
} else {
web3Provider = new Web3.providers.HttpProvider('localhost:8545');
}
web3 = new Web3(web3Provider);
fs.readFile(path.resolve(__dirname, 'TokenERC20.json'), 'UTF-8', function read(err, res) {
if (err) {
throw err;
}
data = JSON.parse(res);
contract = new TruffleContract(data);
contract.setProvider(web3Provider);
});
router.get('/', function(req, res, next) {
res.sendFile('index.html');
});
router.get('/api/v1/wallet/:ens', function(req, res){
const name = req.params.ens.replace(" ", "_");
let result = [];
const iterator = function(value, num, collection) {
return contract
.deployed()
.then(function(deployed) {
return deployed.balanceOf(value.address);
})
.then(function(data) {
return data.toNumber() / Math.pow(10, 18);
})
.then(function(data) {
return result.push({ address: value.address, name: name, balance: data });
})
.catch(function(err) {
console.log(err.message);
});
return Promise.resolve(null);
};
return sequelize.query('SELECT address FROM ens WHERE UPPER(name) LIKE UPPER(1ドル);', { bind: [name], raw: true, type: Sequelize.QueryTypes.SELECT })
.then(function(row) {
return Aigle.resolve(row).each(iterator);
})
.then(function() {
return res.json(result);
})
.catch(function(err) {
console.log(err.message);
});
//return Promise.resolve(null);
});
router.get('/api/v1/wallet', function(req, res){
let result = [];
const iterator = function(value, num, collection) {
return contract
.deployed()
.then(function(deployed) {
return deployed.balanceOf(value.address);
})
.then(function(data) {
return data.toNumber() / Math.pow(10, 18);
})
.then(function(data) {
return result.push({ address: value.address, name: value.name, balance: data });
})
.catch(function(err) {
console.log(err.message);
});
return Promise.resolve(null);
};
return sequelize.query('SELECT address, name FROM ens;', { raw: true, type: Sequelize.QueryTypes.SELECT })
.then(function(row) {
return Aigle.resolve(row).each(iterator);
})
.then(function() {
return res.json(result);
})
.catch(function(err) {
console.log(err.message);
});
//return Promise.resolve(null);
});
router.post('/api/v1/wallet', function(req, res){
const data = { name: req.body.name.replace(" ", "_"), passphrase: req.body.passphrase };
const address = web3.personal.newAccount(data.passphrase);
const bank = "0x49b36fa1772bdb4d9249f43179832ccdb3732ffc";
web3.personal.unlockAccount(bank, "", function(error, res) {
if (error) {
console.log(error.message);
return;
}
var amount = web3.toWei(3.20, "ether")
web3.eth.sendTransaction({ from: bank, to: address, value: amount })
});
return sequelize.query('INSERT INTO ens (address, name) VALUES (1,ドル 2ドル);', { bind: [address, data.name], raw: true, type: Sequelize.QueryTypes.INSERT })
.then(function() {
return res.status(200).json({ address: address });
});
//return Promise.resolve(null);
});
router.post('/api/v1/transaction', function(req, res){
const data = { from: req.body.from.replace(" ", "_"), passphrase: req.body.passphrase, to: req.body.to.replace(" ", "_"), amount: req.body.amount*10**18 };
let result = {};
const iterator = function(value, num, collection) {
return result[value.name] = value.address;
};
return sequelize.query('SELECT address, name FROM ens WHERE UPPER(name) LIKE UPPER(1ドル) OR UPPER(name) LIKE UPPER(2ドル);', { bind: [data.from, data.to], raw: true, type: Sequelize.QueryTypes.SELECT })
.then(function(row) {
return Aigle.resolve(row).each(iterator);
})
.then(function() {
return web3.personal.unlockAccount(result[data.from], data.passphrase)
})
.then(function() {
return contract.deployed()
})
.then(function(deployed) {
return deployed.transfer(result[data.to], data.amount, { from: result[data.from] });
})
.then(function(tx) {
console.log(tx);
return res.status(200).json(tx);
})
.catch(function(err) {
console.log(err.message);
return res.status(500).json(err);
});
//return Promise.resolve(null);
});
module.exports = router;
-
\$\begingroup\$ Hey Afe, I would recommend you this nice article about Javascript promises pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html \$\endgroup\$A. Romeu– A. Romeu2018年03月02日 16:10:00 +00:00Commented Mar 2, 2018 at 16:10
1 Answer 1
Code doesn't look too bad for a first attempt at using promises.
Here are a few observations.
General
- A wholly synchronous step in a promise chain doesn't need its own
.then()
and can be merged downwards with following synchronous step(s) up to and including the next asynchronous step. - Completely flat promise chains are not always the best. Nesting is perfectly acceptable and often offers big benefits in terms of closure and flow control.
- You can avoid clumsy outer vars when it's possible to pass them down a promise chain or keep them in a closure.
- Arrow functions would help make the code more concise (and arguably more readable).
Specific
- The routes take no account of failure of
fs.readFile(path.resolve(__dirname, 'TokenERC20.json'), 'UTF-8')
. - In fixing (1), you can (probably) execute
contract.deployed()
once and deliverdeployed
via adeployedPromise
promise in those routes that use it. - In routes that use
deployed
, you can start withdeployedPromise.then(...)
to ensure that nothing is executed iffs.readFile(...)
orcontract.deployed()
failed. - In
router.post('/api/v1/wallet')
,sequelize.query(...)
doesn't wait forweb3.personal.unlockAccount()
. - The test
if (typeof web3 !== 'undefined')
is a bit odd whenweb3
is guaranteed to be undefined.
With all that in mind, I end up with the following :
const express = require('express');
const path = require('path');
const Web3 = require('web3');
const fs = require('fs');
const TruffleContract = require('truffle-contract');
const Sequelize = require('sequelize');
const Aigle = require('aigle');
const router = express.Router();
// const Op = Sequelize.Op; // not used
const sequelize = new Sequelize('aircoin', 'giuseppe', '12345', {
'dialect': 'postgres',
'host': 'localhost',
'port': 5432,
'operatorsAliases': false
});
let web3;
if (typeof web3 !== 'undefined') { // ???
web3Provider = web3.currentProvider;
} else {
web3Provider = new Web3.providers.HttpProvider('localhost:8545');
}
web3 = new Web3(web3Provider);
let deployedPromise = Aigle.promisify(fs.readFile)(path.resolve(__dirname, 'TokenERC20.json'), 'UTF-8')
.then(res => {
let contract = new TruffleContract(JSON.parse(res));
contract.setProvider(web3Provider);
return contract.deployed();
});
router.get('/', function(req, res, next) {
res.sendFile('index.html');
});
router.get('/api/v1/wallet/:ens', function(req, res) {
return deployedPromise
.then(deployed => {
const name = req.params.ens.replace(' ', '_');
return sequelize.query('SELECT address FROM ens WHERE UPPER(name) LIKE UPPER(1ドル);', { 'bind': [name], 'raw': true, 'type': Sequelize.QueryTypes.SELECT })
.then(row => {
return Aigle.resolve(row).map(value => {
return deployed.balanceOf(value.address)
.then(data => { 'address': value.address, 'name': name, 'balance': data.toNumber() / Math.pow(10, 18) });
});
});
})
.then(result => {
res.json(result);
})
.catch(err => {
console.log(err.message);
});
});
router.get('/api/v1/wallet', function(req, res) {
return deployedPromise
.then(deployed => {
return sequelize.query('SELECT address, name FROM ens;', { 'raw': true, 'type': Sequelize.QueryTypes.SELECT })
.then(row => {
return Aigle.resolve(row).map(value => {
return deployed.balanceOf(value.address)
.then(data => { 'address': value.address, 'name': value.name, 'balance': data.toNumber() / Math.pow(10, 18) })
});
});
})
.then(result => {
res.json(result);
})
.catch(error => {
console.log(err.message);
});
});
router.post('/api/v1/wallet', function(req, res) {
const data = { 'name': req.body.name.replace(' ', '_'), 'passphrase': req.body.passphrase };
const address = web3.personal.newAccount(data.passphrase);
const bank = "0x49b36fa1772bdb4d9249f43179832ccdb3732ffc";
return web3.personal.unlockAccount(bank, '')
.then(() => web3.eth.sendTransaction({ 'from': bank, 'to': address, 'value': web3.toWei(3.20, 'ether') }))
.then(() => sequelize.query('INSERT INTO ens (address, name) VALUES (1,ドル 2ドル);', { 'bind': [address, data.name], 'raw': true, 'type': Sequelize.QueryTypes.INSERT }))
.then(() => {
res.status(200).json({ 'address': address });
})
.catch(error => {
console.log(error.message);
});
});
router.post('/api/v1/transaction', function(req, res) {
return deployedPromise
.then(deployed => {
const data = { 'from': req.body.from.replace(' ', '_'), 'passphrase': req.body.passphrase, 'to': req.body.to.replace(' ', '_'), 'amount': req.body.amount*10**18 };
return sequelize.query('SELECT address, name FROM ens WHERE UPPER(name) LIKE UPPER(1ドル) OR UPPER(name) LIKE UPPER(2ドル);', { 'bind': [data.from, data.to], 'raw': true, 'type': Sequelize.QueryTypes.SELECT })
.then(row => {
let result = {};
return Aigle.resolve(row).each(value => {
result[value.name] = value.address;
})
.then(() => {
return web3.personal.unlockAccount(result[data.from], data.passphrase)
.then(deployed => deployed.transfer(result[data.to], data.amount, { 'from': result[data.from] }));
});
});
})
.then(tx => {
res.status(200).json(tx);
})
.catch(error => {
res.status(500).json(error);
});
});
module.exports = router;
Untested and includes a few assumptions, therefore may not be 100% correct. Be prepred to debug, or just raid for ideas.
Explore related questions
See similar questions with these tags.