remove duplicate code
[squeep-lazy-property] / index.js
1 'use strict';
2
3 /**
4 * Establish some immutable descriptor Shapes with our preferred defaults.
5 * We want normally-behaving properties unless told otherwise.
6 */
7 function defaultShape(proto, ...objs) {
8 const newObject = Object.create(proto);
9 Object.assign(newObject, ...objs);
10 return newObject;
11 }
12
13 const defaultDataDescriptor = defaultShape(null, {
14 configurable: true,
15 enumerable: true,
16 writable: true,
17 value: undefined,
18 });
19
20 const defaultAccessorDescriptor = defaultShape(null, {
21 configurable: true,
22 enumerable: true,
23 get: undefined,
24 set: undefined,
25 });
26
27 /**
28 * Create a new descriptor which inherits our preferred defaults.
29 * @param {Object} defaultDescriptor
30 * @param {...any} objs
31 * @returns {Object}
32 */
33 function descriptorFromDefault(defaultDescriptor, ...objs) {
34 return Object.assign(Object.create(defaultDescriptor), ...objs);
35 }
36
37 /**
38 * Defer calling initializer to set value for name until name is read.
39 * @param {Object} obj
40 * @param {String} name
41 * @param {() => {*}} initializer
42 * @param {Object=} descriptor
43 * @param {Boolean} descriptor.configurable
44 * @param {Boolean} descriptor.enumerable
45 * @param {Boolean} descriptor.writable
46 */
47 function lazy(obj, name, initializer, descriptor) {
48 if (typeof initializer !== 'function') {
49 throw new TypeError('initialize is not callable');
50 }
51
52 // Establish the descriptor for the eventually-initialized property.
53 const finalDescriptor = descriptorFromDefault(defaultDataDescriptor, descriptor);
54
55 // The descriptor defining the interim accessor property.
56 const lazyDescriptor = descriptorFromDefault(defaultAccessorDescriptor, {
57 // The accessor replaces itself with the value returned by invoking the initializer.
58 get: function () {
59 finalDescriptor.value = initializer.apply(obj);
60 Object.defineProperty(obj, name, finalDescriptor);
61 return finalDescriptor.value;
62 },
63
64 // An explicit set overrides the lazy descriptor with a default data property.
65 set: function (value) {
66 Object.defineProperty(obj, name, descriptorFromDefault(defaultDataDescriptor, {
67 value,
68 }))
69 },
70
71 configurable: true, // Ensure we can replace ourself.
72 enumerable: finalDescriptor.enumerable, // Use requested visibility.
73 });
74
75 Object.defineProperty(obj, name, lazyDescriptor);
76 }
77
78 module.exports = {
79 lazy,
80 };