Wrapper to run asynchronous code in a synchronous way.
Let you transform this:
utils.async().then(function() {
utils.sync();
return utils.async();
}).then(function() {
return utils.async();
}).then(function() {
utils.sync();
}).then(function() {
console.log("done");
});
into this:
new SyncBuilder(utils)
.async()
.sync()
.async()
.async()
.sync()
.build(function() {
console.log("done");
});
$ bower sync-builder --save
and include lib/sync-builder.js
or lib/sync-builder.min.js
and use it globally (SyncBuilder
) or using AMD or CommonJS (sync-builder
).
$ npm install sync-builder --save
and use it:
var SyncBuilder = require('sync-builder');
Download the source file: sync-builder.js or production version: sync-builder.min.js.
var utils = {
async: function() {
return new Promise(function(resolve) {
setTimeout(function() {
console.log("async");
resolve();
}, 0);
});
},
sync: function() {
console.log("sync")
}
};
var builder = SyncBuilder(utils);
builder
.async()
.sync()
.async()
.async()
.sync()
.build(function() {
console.log("done");
});
Expected output order:
async
sync
async
async
sync
done
To achieve the same without SyncBuilder
:
utils.async().then(function() {
utils.sync();
return utils.async();
}).then(function() {
return utils.async();
}).then(function() {
utils.sync();
}).then(function() {
console.log("done");
});
Let's consider John, who has to work to earn money. John spends 1 second to get 500 cents:
var Person = function(budget, salary) {
this.budget = budget;
this.salary = salary;
};
Person.prototype = {
goToWork: function() {
return new Promise(function(resolve) {
setTimeout(function() {
this.budget += this.salary;
resolve();
}.bind(this), 1000);
}.bind(this));
},
printBudget: function() {
console.log(this.budget);
}
};
If we run code synchronously, we don't get desired results:
var johnKowalski = new Person(1000, 500);
johnKowalski.goToWork(); // adds 500 to budget after 1 second
johnKowalski.printBudget(); // prints 1000 immediately
Using the wrapper, we get the expected result:
var johnKowalski = new Person(1000, 500);
var builder = new SyncBuilder(johnKowalski);
builder
.goToWork()
.printBudget()
.build(); // prints 1500 after 1 second
I had a set of utils method to test my component:
// given component
var testUtils = {
checkWidth: function(width) {
equal(component.width, width);
},
click: function() {
component.click(); // async, ready on 'ready' event
},
move: function(x) {
component.move(); // async, ready on 'moved' event
}
}
To get it working, I had to use promises:
// given component
var testUtils = {
checkWidth: function(width) {
equal(component.width, width);
},
click: function() {
component.click(); // async
return new Promise(function(resolve) {
component.on('ready', function() {
resolve();
}
});
},
move: function(x) {
component.move(); // async
return new Promise(function(resolve) {
component.on('moved', function() {
resolve();
});
});
}
}
The best I could do with then
chaining was:
testUtils.checkWidth(100);
testUtils.click().then(function() {
testUtils.checkWidth(100);
return testUtils.move();
}).then(function() {
testUtils.checkWidth(150);
return testUtils.click();
}).then(function() {
testUtils.checkWidth(100);
});
which is not bad but clearly not readable... Using SyncBuilder
, we can achieve the same functionality in a readable way:
var testUtilsBuilder = new SyncBuilder(testUtils);
testUtilsBuilder
.checkWidth(100)
.click()
.checkWidth(100)
.move()
.checkWidth(150)
.click()
.checkWidth(150)
.build();
Isn't it more understandable? :)
SyncBuilder wraps all the methods from your object (whether it's a literal or an instance) and make it public to you.
If you want to create synchronous function, you should not return anything.
If you want to create a asynchronous function, you should return a Promise
.
The only requirement for being a Promise
is to have a then
function that is called after your asyncronous code was run.
var utils = {
sync: function() {
// your code
},
async: function() {
// your async code
return promise;
}
}
Don't forget to call build()
method at the end!
new SyncBuilder().async().sync().build();
build()
method itself takes a callback to be called after all the methods:
new SyncBuilder().async().sync().build(function() {
console.log("I'm done");
});
You can pass a context and arguments to build()
method as well:
new SyncBuilder().async().sync().build(function(a1, a2) {
// this === context
// a1 === arg1
// a2 === arg2
console.log("I'm done");
}, context, arg1, arg2);
You can pass arguments to your functions:
var utils = {
async: function(text) {
return new Promise(function(resolve) {
setTimeout(function() {
console.log(text);
resolve();
}, 0);
});
},
sync: function(text, number) {
console.log(text, number)
}
};
new SyncBuilder(utils)
.async("I'm asynchronous")
.sync("Sync here", 1)
.sync("Again sync", 2)
.build();
the result:
I'm asynchronous
Sync here 1
Again sync 2
In the examples, I used ES6 Promises syntax. Here is which browsers support it at the moment: http://kangax.github.io/compat-table/es6/#Promise.
You can use any other library like q or jQuery promise.