-
Notifications
You must be signed in to change notification settings - Fork 11
Description
This is a continuation of conversations had in:
On the call I offered to model some hypothetical API calls to a currently non-existent ECMA-419 asynchronous read API. To be clear, these are examples of I2C reads from the user's perspective. The inner workings of the read implementation are left to the implementor. We're using callbacks since not all target platforms will support promises, and error first call backs can easily be "promisified" in other host environments.
Existing ECMA-419 implementation
Synchronous
read(option[, stop])
Param. | Required | Range | Default |
---|---|---|---|
option | yes* | positive integer, ArrayBuffer, TypedArray | |
stop | no | true or false | true |
- The number of readable bytes is undefined so option is required
let result = myI2C.read(64);
This pattern makes perfect sense for embedded systems, or anywhere reads are fast enough not to be a problem while blocking.
Possible async patterns
Callback param
readAsync(option[, stop], cb)
Param. | Required | Range | Default |
---|---|---|---|
option | yes | positive integer (number of bytes) | |
*sto | no | true or false. | |
cb | yes | err first callback |
myI2C.readAsync(64, (err, data) => {
// Do things with the data
});
This seems like the most practical option. By adding a distinct, optional method for asynchronous reads the absence of the method should throw in an unambiguous way. All of the other options listed below could result in implementations that do not throw an error but also do not behave as expected.
Variadic read
read(option[, stop][, cb])
Param. | Required | Range | Default |
---|---|---|---|
option | yes | positive integer (number of bytes) | |
stop | no | true or false | true |
cb | no | err first callback* | null |
myI2C.read(64, (err, data) => {
// Do things with the data
});
I believe that variadic functions make implementation, documentation, and support more difficult. I do not think we should use them here.
Leveraging onReadable
read(option[, stop])
Param. | Required | Range | Default |
---|---|---|---|
option | yes | positive integer (number of bytes) | |
stop | no | true or false | true |
First read returns, when read is complete onReadable fires. This requires user code to manage state:
- Does each read trigger a read or process the results of a read?
- What register address was last read and which read request does this onReadable correspond to?
const myI2C = new device.I2C({
...device.I2C.default,
onReadable: () => {
let data = myI2C.read(64);
if data === null return;
// do something with the data
}
});
let data = myI2C.read(64); // expets null
Because of the burden put on the user, I do not think this is a good pattern.
Adding an onread property
read(option[, stop])
Param. | Required | Range | Default |
---|---|---|---|
option | yes | positive integer (number of bytes) | |
stop | no | true or false | true |
Read only triggers, when read is complete onReadable fires. This requires user code to manage state:
- Does each read trigger a read or process the results of a read?
- What register address was last read and which read request does this onReadable correspond to?
const myI2C = new device.I2C({
...device.I2C.default,
onReadable: () => {
this.read(64);
},
onRead: (data) => {
if data === null return;
// do something with the data
}
});
let data = myI2C.read(64); // expets null
Because of the burden put on the user, I do not think this is a good pattern