How to add a custom property or method to a promise? - javascript

Regular promises have the beloved .then() and .catch() functions.
When promising to retrieve an object that itself has properties that return promises we find chains of promises such as the following:
require("clientside-view-loader")
.then((view)=>
return view.load("clientside-view-modal-login_signup");
})
.then((compiler)=>{
return compiler.generate()
})
.then((modal)=>{
document.body.appendChild(modal);
modal.show("login");
})
This is UGLY!
How can we modify a promise to attach a custom property so that we can convert the above into the following?
require("clientside-view-loader")
.load("clientside-view-modal-login_signup")
.generate()
.then((modal)=>{
document.body.appendChild(modal);
modal.show("login");
})
note, these examples use the clientside-require require and not the nodejs require

How can we modify a promise to attach a custom property so that we can convert the above into the following?
You don't modify promises at all. You just implement the builder pattern for the above promise chain.
class ClientSideViewLoader {
constructor(p = Promise.resolve()) {
this.promise = p;
}
static init() {
return new this(require("clientside-view-loader"));
}
load(x) {
return new this.constructor(this.promise.then(view =>
view.load(x)
));
}
generate() {
return new this.constructor(this.promise.then(compiler =>
compiler.generate()
));
}
then(...args) {
return this.promise.then(...args);
}
}
ClientSideViewLoader.init()
.load("clientside-view-modal-login_signup")
.generate()
.then(modal => {
document.body.appendChild(modal);
modal.show("login");
})
No need to do anything complicated like subclassing Promise. If you want, you can also dynamically generate all these methods.
This is UGLY!
Well, if you were looking for beautiful promise code, you would simply use modern async/await syntax instead of then callbacks:
const view = await require("clientside-view-loader");
const compiler = await view.load("clientside-view-modal-login_signup");
const modal = await compiler.generate();
document.body.appendChild(modal);
modal.show("login");

Your initial code can be made shorter and more readable simply by using different syntax for your arrow functions. These two rules of arrow function syntax are relevant:
parentheses are optional around the only argument of single-argument functions
single-statement functions that return a value can have the {} and the return removed
Thus, you could write your code like this, with the short form view => … instead of (view) => { return …; }:
require("clientside-view-loader")
.then(view => view.load("clientside-view-modal-login_signup"))
.then(compiler => compiler.generate())
.then(modal => {
document.body.appendChild(modal);
modal.show("login");
});

If you know the properties you wish to add in advance you can simply append a property to the promise like you would any other object:
view_loader.load = function(path){
return this.then((view_loader)=>{
return view_loader.load(path)
})
}
view_loader.load(...) // now works!
Here's a function that does this for a dynamic set of properties:
function modify_orig_promise(original_promise, properties_to_append){
var blacklist = ["then", "catch", "spread"];
var function_keys = Object.keys(properties_to_append);
for(var i = 0; i < function_keys.length; i++){
var function_key = function_keys[i];
if(blacklist.indexOf(function_key) > -1) {
console.warn("properties_to_append in require(__, {functions : {} }) included a blacklisted function name : `"+key+"`. skipping this property.")
} else {
var requested_function = properties_to_append[function_key];
original_promise[function_key] = requested_function; // append the function to the promise
}
}
return original_promise;
}
Then
var properties_to_append = {
load : function(path){
return this.then((view_loader)=>{ return view_loader.load(path)})
}
}
modified_require = modify_orig_promise(require("clientside-view-loader"), properties_to_append);
modified_require.load("clientside-view-modal-login_signup") // Works
If you dont know the properties in advance (e.g., the properties are determined from a promise) you'll need to use a proxy that waits until that promise resolves to respond. This is answered here: How to add properties to a promise asynchronously?

Related

Why would you ever call .call() on Observable functions?

I am a relative beginner in Angular, and I am struggling to understand some source I am reading from the ng-bootstrap project. The source code can be found here.
I am very confused by the code in ngOnInit:
ngOnInit(): void {
const inputValues$ = _do.call(this._valueChanges, value => {
this._userInput = value;
if (this.editable) {
this._onChange(value);
}
});
const results$ = letProto.call(inputValues$, this.ngbTypeahead);
const processedResults$ = _do.call(results$, () => {
if (!this.editable) {
this._onChange(undefined);
}
});
const userInput$ = switchMap.call(this._resubscribeTypeahead, () => processedResults$);
this._subscription = this._subscribeToUserInput(userInput$);
}
What is the point of calling .call(...) on these Observable functions? What kind of behaviour is this trying to achieve? Is this a normal pattern?
I've done a lot of reading/watching about Observables (no pun intended) as part of my Angular education but I have never come across anything like this. Any explanation would be appreciated
My personal opinion is that they were using this for RxJS prior 5.5 which introduced lettable operators. The same style is used internally by Angular. For example: https://github.com/angular/angular/blob/master/packages/router/src/router_preloader.ts#L91.
The reason for this is that by default they would have to patch the Observable class with rxjs/add/operators/XXX. The disadvantage of this is that some 3rd party library is modifying a global object that might unexpectedly cause problems somewhere else in your app. See https://github.com/ReactiveX/rxjs/blob/master/doc/lettable-operators.md#why.
You can see at the beginning of the file that they import each operator separately https://github.com/ng-bootstrap/ng-bootstrap/blob/master/src/typeahead/typeahead.ts#L22-L25.
So by using .call() they can use any operator and still avoid patching the Observable class.
To understand it, first you can have a look at the predefined JavaScript function method "call":
var person = {
firstName:"John",
lastName: "Doe",
fullName: function() {
return this.firstName + " " + this.lastName;
}
}
var myObject = {
firstName:"Mary",
lastName: "Doe",
}
person.fullName.call(myObject); // Will return "Mary Doe"
The reason of calling "call" is to invoke a function in object "person" and pass the context to it "myObject".
Similarly, the reason of this calling "call" below:
const inputValues$ = _do.call(this._valueChanges, value => {
this._userInput = value;
if (this.editable) {
this._onChange(value);
}
});
is providing the context "this._valueChanges", but also provide the function to be called base on that context, that is the second parameter, the anonymous function
value => {
this._userInput = value;
if (this.editable) {
this._onChange(value);
}
}
In the example that you're using:
this._valueChanges is the Input Event Observerable
The _do.call is for doing some side affects whenever the event input happens, then it returns a mirrored Observable of the source Observable (the event observable)
UPDATED
Example code: https://plnkr.co/edit/dJNRNI?p=preview
About the do calling:
You can call it on an Observable like this:
const source = Rx.Observable.of(1,2,3,4,5);
const example = source
.do(val => console.log(`BEFORE MAP: ${val}`))
.map(val => val + 10)
.do(val => console.log(`AFTER MAP: ${val}`));
const subscribe = example.subscribe(val => console.log(val));
In this case you don't have to pass the first parameter as the context "Observable".
But when you call it from its own place like you said, you need to pass the first parameter as the "Observable" that you want to call on. That's the different.
as #Fan Cheung mentioned, if you don't want to call it from its own place, you can do it like:
const inputValues$=this._valueChanges.do(value=>{
this._userInput = value;
if (this.editable) {
this._onChange(value);
}
})
I suppose
const inputValues$ = _do.call(this._valueChanges, value => {
this._userInput = value;
if (this.editable) {
this._onChange(value);
}
});
is equivalent to
const inputValues$=this._valueChanges.do(value=>{
this._userInput = value;
if (this.editable) {
this._onChange(value);
}
})
In my opinion it's not an usual pattern(I think it is the same pattern but written in different fashion) for working with observable. _do() in the code is being used as standalone function take a callback as argument and required to be binded to the scope of the source Observable
https://github.com/ReactiveX/rxjs/blob/master/src/operator/do.ts

inside Array.push without a function call

I want to perform more logic before writing an element to an array:
tempDatensatz.push( () => {
var current = window.dataForDataTable[i].outbounds[key].updatedAt;
if (current) {
return current.toString();
} else {
return "".toString();
}
});
Getting the value from that array will be achieved like this:
tempDatensatz[0]()
But I want the same logic in it without having a function to call. I need a normal array, where I get a value like this:
tempDatensatz[0]
What can I do instead?
Updated
I published my project to gitHub, you can take a look if you need a better understanding :)
https://github.com/te2020/GoEuro/blob/master/GoEuro/Views/Home/Index.cshtml
Use an immediately invoked function instead of just a function:
tempDatensatz.push( (function(){
var current = window.dataForDataTable[i].outbounds[key].updatedAt;
if (current) {
return current.toString();
} else {
return "".toString();
}
})());
The function will be executed immediatly after it definition, returning the result. So push won't push a reference to that function but instead it will push it returned value (the result).
You can write a proxy as follows:
function makeProxy(array) {
return new Proxy(array, {
get(target, property) {
return !isNaN(property) ? target[property]() : target[property];
}
});
}
const tempDatensatz = [];
const useThisOne = makeProxy(tempDatensatz);
useThisOne.push(() => alert("Hi, Jane!"));
useThisOne[0];
Pushing/writing to the array will work as expected, but retrieving its elements will go through the get handler, which will execute the function.
You could just use an expression, like:
tempDatensatz.push(
(window.dataForDataTable[i].outbounds[key].updatedAt || '').toString();
);
For more complex expressions you can often use the ternary operator. For the above that would look like this:
tempDatensatz.push(
window.dataForDataTable[i].outbounds[key].updatedAt
? window.dataForDataTable[i].outbounds[key].updatedAt.toString()
: ''
);
Your code
When looking at the github code you linked to, you can do all that pushing with this "oneliner":
var tempDatensatz =
['companyId', 'mode', 'duration', 'outboundId', 'journeyId', 'departureTime',
'arrivalTime', 'stops', 'price', 'updatedAt', 'segments']
.map( prop => (window.dataForDataTable[i].outbounds[key][prop] || '').toString() );

Understanding Methods Chaining in Javascript

I'm new to ES6 and Javascript and I can't figure out what's wrong with chaining this dump() method in the following piece of code.
It returns "main.js:25 Uncaught TypeError: Cannot read property 'dump' of undefined":
class TaskCollection {
constructor(tasks = []) {
this.tasks = tasks;
}
addTasks(newTasks = []) {
this.tasks = this.tasks.concat(newTasks);
}
dump() {
console.log(this.tasks);
}
}
let myTasks = new TaskCollection([
'Do stuff'
]);
myTasks.addTasks([
'New Task'
]).dump();
Now if I don't chain that dump() method, everything would work just fine.
myTasks.addTasks([
'New Task'
]);
myTasks.dump();
Method addTasks is not returning a reference to the object. If you want chaining to work, your method needs to look like this:
addTasks(newTasks = []) {
this.tasks = this.tasks.concat(newTasks);
return this;
}
In order to use method chaining, you need to return this from the earlier method. In your case, you don't return this from addTasks, so the result of calling addTasks is undefined, and you can't call methods on undefined.
So just add
return this;
...to any method you want to be able to chain from.
Method chaining is not something special. When you do:
addTasks(/*...*/).dump();
what you're doing is effectively:
var x = addTasks(/*...*/);
x.dump();
...just without the variable. Depending on how addTasks is written, you might be calling dump on the same object (method chaining) or on some other object entirely (if addTasks returned something other than this).
You should return this in *addTasks* method
class TaskCollection {
constructor(tasks = []) {
this.tasks = tasks;
}
addTasks(newTasks = []) {
this.tasks = this.tasks.concat(newTasks);
return this;
}
dump() {
console.log(this.tasks);
}
}
let myTasks = new TaskCollection([
'Do stuff'
]);
myTasks.addTasks([
'New Task'
]).dump();

Accessing values of promises in an array

I'm trying to create an array from http requests that holds a name property and two promises: one as an array and the other as an object. I'm able to get the info I need using this approach but I'm not able to access it to display it in the scope of the html. For instance, when I log out the array, "people" I get an array of objects (looking like: [Object, Object, Object]) and I have to expand a bunch of things to see the actual values of each object so that "person.skills" would really have to be "person.skills.$$state.value". Also, on the page, {{person.name}} will show up but the other two are just empty objects that look like this: {}. So how can I access the values of the promises so that I can just use {{person.skills}} to show the array?
js
var getPeople = function() {
var named = $q.defer();
$http.get('/getnames').success(function (response) {
named.resolve(response);
});
return named.promise;
};
getPeople().then(function(namesRes) {
var people = [];
names = namesRes;
names.forEach(function(index){
var name = index;
var count = $q.defer();
var skills = $q.defer();
var urls = '/getskillsbyname/' + name;
var urlc = '/getcountbyname/' + name;
$http.get(urls).success(function (response) {
skills.resolve(response);
});
$http.get(urlc).success(function (response) {
count.resolve(response);
});
people.push({name:name, skills:skills.promise, count:count.promise});
});
return people;
}).then(function(people) {
console.log(people);
$scope.people = people;
});
html
<div ng-repeat="person in people">
<p>{{person.name}}</p>
<p>{{person.skills}}</p>
<p>{{person.count}}</p>
</div>
Your method not returning promise correctly, you need to use $q for waiting till all the inner promises get completed.
I have implemented your code by maintaining grand promise variable in the forEach loop, whenever asking skills and couts call are made, it put that call inside $q.all and $q.all promise is moved to grandPromiseArray.
var getPeople = function() {
return $http.get('/getnames');
};
getPeople().then(function(response) {
var people = [];
names = response.data;
grandPromiseArray = [];
names.forEach(function(index) {
var name = index, count = $q.defer(), skills = [],
urls = '/getskillsbyname/' + name, urlc = '/getcountbyname/' + name;
grandPromiseArray.push(
$q.all([$http.get(urls), $http.get(urlc)])
.then(function(response) {
people.push({
name: name,
skills: response[0].data, //response[0] value returned by 1st promise
count: response[1].data //response[1] value returned by 2nd promise
});
})
);
});
return $q.all(grandPromiseArray).then(function() {
return people
});
})
.then(function(people) {
console.log(people);
$scope.people = people;
});
Example for a valid promise chain:
function getSomeData(){
var defer = $q.defer();
$http.get(urls).success(function (response) {
defer.resolve(response);
});
return defer.promise;
}
And then you can access the promise data like:
getSomeData().then(function(data){
console.log(data); // This is the response of the $http request
});
Organize your requests in functions and it looks much cleaner.
$http also returns promise by default, but I recommend to create a service/factory, which doing your requests, then you need the defer.
$http.get(urls).then(function(data){
});
A Promise isn't going to automatically allow you to display your results in Angular.
A Promise is an object, which lets you chain async operations together, by passing in functions which are eventually fired and passed the value.
You can't ask for myPromise.value and expect it to be there, because it's an async process which might take 20ms, 2mins, or might just never come back at all.
In terms of how this is structured, it might be much cleaner and easier to reason about if you broke the data-fetching parts out into a service, and just injected the service into the controller.
I'm not sure which version of Angular you've been working with, but I'm hoping its at least 1.2.
Also, my example is using the native Promise constructor, but I'm sure Angular's version of $q now has everything that's needed, if you aren't using a promise polyfill.
function PersonService (http) {
function getResponseData (response) { return response.data; }
function getURL (url) { return http.get(url).then(getResponseData); }
function makePerson (personData) {
return {
name: personData[0],
skills: personData[1],
count: personData[2]
};
}
var personService = {
getNames: function () { return getURL("/names/"); },
getSkills: function (name) { return getURL("/getskillsbyname/" + name); },
getCounts: function (name) { return getURL("/getcountsbyname/" + name); },
loadPerson: function (name) {
return Promise.all([
Promise.resolve(name),
personService.getSkills(name),
personService.getCount(name)
]).then(makePerson);
},
loadPeople: function (names) {
return Promise.all(names.map(personService.loadPerson));
}
};
return personService;
}
Here's what my person service might look like.
I've written a couple of super-small helper functions that keep getting reused.
Then I've made the service all about getting either people or details about people.
I'm using Promise.all, and passing it an array of promises. When every promise in the array is complete, it returns an array of all of the data returned by the promises. Promise.resolve is used in one spot. It basically returns a promise which succeeds automatically, with the value it was given. That makes it really useful, when you need a promise to start a chain, but you don't have to do anything special, aside from returning a value you already have.
My assumption is both that q now names its methods the same way the spec does, and that Angular's implementation of $q follows the spec as well.
function MyController (personService) {
var controller = this;
controller.people = [];
controller.error = false;
init();
function setPeople (people) {
controller.people = people || [];
}
function handleError (err) {
setPeople([]);
controller.error = true;
}
function init () {
return personService.getNames()
.then(personService.loadPeople)
.then(setPeople)
.catch(handleError);
}
}
My controller now gets really, really simple. It's using the service to get names, and load people, and then it sets them, and it's ready to go. If there was an error, I handle it in whatever way makes sense.
Handling the injection of this stuff is pretty quick and easy:
angular.module("myExample")
.service("PersonService", ["$http", PersonService])
.controller("MyController", ["PersonService", MyController]);
Using this on the page is now painless, as well:
<div ng-controller="MyController as widget">
<ul ng-hide="widget.people.length == 0">
<li ng-repeat="person in widget.people">
<person-details person="person"></person-details>
</li>
</ul>
<div ng-show="widget.error">Sorry, there was an error with your search.</div>
</div>
I think you could probably use $q.all to construct your people:
var p = $q.all({
name: $q.when(name),
skills: $http.get(urls),
count: $http.get(urlc)
});
p.then(function(person) {
people.push(person);
});
$q.all will construct a new promise that gets resolved when all of the input promises resolve. Since we input a map, $q.all will resolve with a map that has the same keys. The values are the resolutions of the corresponding promises. In this case, that's a person so we can then just push it into the people array directly.
This is a slightly naive implementation. Since the calls are asynchronous, there is no guarantee that the order of names will be preserved in people -- but it shouldn't be too hard for you to fix that if it is important.
Create an array of all the promises for the counts and skills. Have broken those requests up into their own functions for readability
Also then() of $http returns a promise object that has a property data
var getPeople = function() {
return $http.get('/getnames').then(function(response) {
return response.data;
});
};
getPeople().then(function(people) {
var promises = [];
people.forEach(function(person) {
// push new requests to promise array
promises.push(getSkills(person));
promises.push(getCount(person))
});
// when all promises resolved return `people`
return $q.all(promises).then(function() {
// return `people` to next `then()`
return people;
});
}).then(function(people) {
console.log(people);
$scope.people = people;
}).catch(function(){
// do something if anything gets rejected
});
function getCount(person) {
var urlc = '/getcountbyname/' + person.name;
return $http.get(urlc).then(function(response) {
person.count = response.data
});
}
function getSkills(person) {
var urls = '/getskillsbyname/' + person.name;
return $http.get(urls).then(function(response) {
person.skills = response.data
});
}

Returning Value from nested Promise.alls

I'm asking this question because I might have a fundamental misunderstanding of how bluebird's Promise.all works:
I'm having trouble understanding how I return the value from nested, dependent, Promise.alls.
I can get the desired result to display in console though. (you can see the data that I'm trying to return in the commented console.log).
For context, I'm writing a Hexo plugin that gets a collection of related blog posts then returns five of them.
Each promise depends on the data returned from the previous promise.
var Promise = require('bluebird')
var _ = require('underscore')
hexo.extend.helper.register("related_posts", function (site) {
var site = site
var post = this.page
var tags = post.tags
var title = post.title
var tagList = tags.map(function(tag){
return tag.name
})
// get 5 posts from each group and add them to a posts array
var PostsArray = []
Promise.all(tagList).then(function(items){
items.forEach(function(theTag){
PostsArray.push(site.tags.findOne({name: theTag}).posts.sort('date', -1).limit(25).toArray())
Promise.all(PostsArray).then(function(posts){
var thePosts = _.flatten(posts)
var finalListOfPosts = []
thePosts.forEach(function(post){
if(post.title != title){
finalListOfPosts.push(post)
}
})
Promise.all(finalListOfPosts).then(function(posts){
var relatedPosts = _.first(_.shuffle(posts), 5)
// MY DATA IS CONSOLE.LOGGED AS I WOULD EXPECT
// BUT HOW DO I RETURN IT?
console.log(relatedPosts)
})
})
})
})
});
Promises work by return value. Just like regular functions. If you then a promise the value you return from the then is the value the outer promise will assume:
var p = Promise.resolve().then(() => { // resolve creates a new promise for a value
return 3; // can also return another promise here, it'll unwrap
});
p.then(alert); //alerts 3
For this, if you have a nested chain (you never have to nest more than 3 levels) - you need to return from it all the way in order to access the value:
return Promise.map(tagList, function(name){ // map is a bluebird utility
return site.tags.findOne({name: name}).posts.sort('date', -1).limit(25).toArray();
}).
then(_.flatten). // flatten the list
filter(function(item){
return item.title !== title; // filter - also a bluebird utility, like Array#filter
}).then(function(posts){
return _.first(_.shuffle(posts), 5);
});

Categories