binary draft
|
@ -69,8 +69,9 @@ new Promise(function(resolve, reject) {
|
||||||
|
|
||||||
The value returned by `.then` is a promise, that's why we are able to add another `.then` at `(2)`. When the value is returned in `(1)`, that promise becomes resolved, so the next handler runs with the value.
|
The value returned by `.then` is a promise, that's why we are able to add another `.then` at `(2)`. When the value is returned in `(1)`, that promise becomes resolved, so the next handler runs with the value.
|
||||||
|
|
||||||
Please note: technically we can also add many `.then` to a single promise. This is not chaining:
|
**A classic newbie error: technically we can also add many `.then` to a single promise. This is not chaining.**
|
||||||
|
|
||||||
|
For example:
|
||||||
```js run
|
```js run
|
||||||
let promise = new Promise(function(resolve, reject) {
|
let promise = new Promise(function(resolve, reject) {
|
||||||
setTimeout(() => resolve(1), 1000);
|
setTimeout(() => resolve(1), 1000);
|
||||||
|
@ -92,7 +93,7 @@ promise.then(function(result) {
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
What we did here is just several handlers to one promise. They don't pass the result to each other, instead they process it idependantly.
|
What we did here is just several handlers to one promise. They don't pass the result to each other, instead they process it independently.
|
||||||
|
|
||||||
Here's the picture (compare it with the chaining above):
|
Here's the picture (compare it with the chaining above):
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
Promise handlers `.then`/`.catch`/`.finally` are always asynchronous.
|
Promise handlers `.then`/`.catch`/`.finally` are always asynchronous.
|
||||||
|
|
||||||
Even when a Promise is immediately resolved, the code on the lines *below* your `.then`/`.catch`/`.finally` may still execute first.
|
Even when a Promise is immediately resolved, the code on the lines *below* your `.then`/`.catch`/`.finally` will still execute first.
|
||||||
|
|
||||||
Here's the code that demonstrates it:
|
Here's the code that demonstrates it:
|
||||||
|
|
||||||
|
@ -23,11 +23,11 @@ Why `.then` triggered after? What's going on?
|
||||||
|
|
||||||
# Microtasks
|
# Microtasks
|
||||||
|
|
||||||
Asynchronous tasks need proper management. For that, the standard specifies an internal queue `PromiseJobs`, more often called the "microtask queue" (v8 term).
|
Asynchronous tasks need proper management. For that, the standard specifies an internal queue `PromiseJobs`, more often referred to as "microtask queue" (v8 term).
|
||||||
|
|
||||||
As said in the [specification](https://tc39.github.io/ecma262/#sec-jobs-and-job-queues):
|
As said in the [specification](https://tc39.github.io/ecma262/#sec-jobs-and-job-queues):
|
||||||
|
|
||||||
- The queue is first-in-first-out: tasks that get enqueued first are run first.
|
- The queue is first-in-first-out: tasks enqueued first are run first.
|
||||||
- Execution of a task is initiated only when nothing else is running.
|
- Execution of a task is initiated only when nothing else is running.
|
||||||
|
|
||||||
Or, to say that simply, when a promise is ready, its `.then/catch/finally` handlers are put into the queue. They are not executed yet. Javascript engine takes a task from the queue and executes it, when it becomes free from the current code.
|
Or, to say that simply, when a promise is ready, its `.then/catch/finally` handlers are put into the queue. They are not executed yet. Javascript engine takes a task from the queue and executes it, when it becomes free from the current code.
|
||||||
|
@ -58,7 +58,7 @@ Now the order is as intended.
|
||||||
|
|
||||||
Browser Javascript, as well as Node.js, is based on an *event loop*.
|
Browser Javascript, as well as Node.js, is based on an *event loop*.
|
||||||
|
|
||||||
"Event loop" is a process when the engine sleeps waits for events.
|
"Event loop" is a process when the engine sleeps and waits for events, then reacts on those and sleeps again.
|
||||||
|
|
||||||
Examples of events:
|
Examples of events:
|
||||||
- `mousemove`, a user moved their mouse.
|
- `mousemove`, a user moved their mouse.
|
||||||
|
@ -71,9 +71,11 @@ Things happen -- the engine handles them -- and waits for more to happen (while
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
When an event happens, and the engine is busy, it gets into a so-called "macrotask queue" (v8 term).
|
As you can see, there's also a queue here. A so-called "macrotask queue" (v8 term).
|
||||||
|
|
||||||
For instance, while the engine is busy processing a network `fetch`, a user may move their mouse causing `mousemove`, and `setTimeout` is due and so on, just as painted on the picture above.
|
When an event happens, and the engine is busy, the event is enqueued.
|
||||||
|
|
||||||
|
For instance, while the engine is busy processing a network `fetch`, a user may move their mouse causing `mousemove`, and `setTimeout` may be due and so on, just as painted on the picture above.
|
||||||
|
|
||||||
Events from the macrotask queue are processed on "first came – first served" basis. When the engine browser finishes with `fetch`, it handles `mousemove` event, then `setTimeout` handler, and so on.
|
Events from the macrotask queue are processed on "first came – first served" basis. When the engine browser finishes with `fetch`, it handles `mousemove` event, then `setTimeout` handler, and so on.
|
||||||
|
|
||||||
|
@ -120,15 +122,19 @@ Promise.resolved()
|
||||||
|
|
||||||
Naturally, `promise` shows up first, because `setTimeout` macrotask awaits in the less-priority macrotask queue.
|
Naturally, `promise` shows up first, because `setTimeout` macrotask awaits in the less-priority macrotask queue.
|
||||||
|
|
||||||
|
**As a side effect, macrotasks are handled only when promises give the engine a "free time".**
|
||||||
|
|
||||||
|
So call have a promise chain that doesn't wait for anything, then things like `setTimeout` or event handlers can never get in the middle.
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
Promise handling is always asynchronous, as all promise actions pass through the internal "promise jobs" queue, also called "microtask queue" (v8 term).
|
- Promise handling is always asynchronous, as all promise actions pass through the internal "promise jobs" queue, also called "microtask queue" (v8 term).
|
||||||
|
|
||||||
**So, `.then/catch/finally` is called after the current code is finished.**
|
**So, `.then/catch/finally` is called after the current code is finished.**
|
||||||
|
|
||||||
If we need to guarantee that a piece of code is executed after `.then/catch/finally`, it's best to add it into a chained `.then` call.
|
If we need to guarantee that a piece of code is executed after `.then/catch/finally`, it's best to add it into a chained `.then` call.
|
||||||
|
|
||||||
There's also a "macrotask queue" that keeps various events, network operation results, `setTimeout`-scheduled calls, and so on. These are also called "macrotasks" (v8 term).
|
- There's also a "macrotask queue" that keeps various events, network operation results, `setTimeout`-scheduled calls, and so on. These are also called "macrotasks" (v8 term).
|
||||||
|
|
||||||
The engine uses the macrotask queue to handle them in the appearance order.
|
The engine uses the macrotask queue to handle them in the appearance order.
|
||||||
|
|
||||||
|
@ -136,4 +142,4 @@ The engine uses the macrotask queue to handle them in the appearance order.
|
||||||
|
|
||||||
In other words, they have lower priority.
|
In other words, they have lower priority.
|
||||||
|
|
||||||
So the order is: regular code, then promise handling, then everything else.
|
So the order is: regular code, then promise handling, then everything else, like events etc.
|
||||||
|
|
|
@ -307,7 +307,7 @@ async function f() {
|
||||||
})();
|
})();
|
||||||
```
|
```
|
||||||
|
|
||||||
There's no ambiguity here: `await` always finishes first.
|
There's no ambiguity here: `await` always finishes first, because (as a microtask) it has a higher priority than `setTimeout` handling.
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ libs:
|
||||||
|
|
||||||
IndexedDB is a built-in database, much more powerful than `localStorage`.
|
IndexedDB is a built-in database, much more powerful than `localStorage`.
|
||||||
|
|
||||||
- Also key/value storage, but not only strings: value can be (almost) anything, multiple key types.
|
- Key/value storage not only strings: value can be (almost) anything, multiple key types.
|
||||||
- Supports transactions for reliability.
|
- Supports transactions for reliability.
|
||||||
- Supports key range queries, indexes.
|
- Supports key range queries, indexes.
|
||||||
- Can store much more data than `localStorage`.
|
- Can store much more data than `localStorage`.
|
||||||
|
@ -16,7 +16,7 @@ That power is usually excessive for traditional client-server apps. IndexedDB is
|
||||||
|
|
||||||
The native interface to IndexedDB, described in the specification <https://www.w3.org/TR/IndexedDB>, is event-based.
|
The native interface to IndexedDB, described in the specification <https://www.w3.org/TR/IndexedDB>, is event-based.
|
||||||
|
|
||||||
We can also use `async/await`-based interface with the help of a promise-based wrapper, like <https://github.com/jakearchibald/idb>. That's really convenient, but the wrapper is not perfect, it can't replace events for all cases, so we'll start with events, and then use the wrapper.
|
We can also use `async/await` with the help of a promise-based wrapper, like <https://github.com/jakearchibald/idb>. That's pretty convenient, but the wrapper is not perfect, it can't replace events for all cases, so we'll start with events, and then use the wrapper.
|
||||||
|
|
||||||
## Open database
|
## Open database
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
function concat(arrays) {
|
||||||
|
// sum of individual array lengths
|
||||||
|
let totalLength = arrays.reduce((acc, value) => acc + value.length, 0);
|
||||||
|
|
||||||
|
if (!arrays.length) return null;
|
||||||
|
|
||||||
|
let result = new Uint8Array(totalLength);
|
||||||
|
|
||||||
|
// for each array - copy it over result
|
||||||
|
// next array is copied right after the previous one
|
||||||
|
let length = 0;
|
||||||
|
for(let array of arrays) {
|
||||||
|
result.set(array, length);
|
||||||
|
length += array.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
function concat(arrays) {
|
||||||
|
// ...your code...
|
||||||
|
}
|
||||||
|
|
||||||
|
let chunks = [
|
||||||
|
new Uint8Array([0, 1, 2]),
|
||||||
|
new Uint8Array([3, 4, 5]),
|
||||||
|
new Uint8Array([6, 7, 8])
|
||||||
|
];
|
||||||
|
|
||||||
|
console.log(Array.from(concat(chunks))); // 0, 1, 2, 3, 4, 5, 6, 7, 8
|
||||||
|
|
||||||
|
console.log(concat(chunks).constructor.name); // Uint8Array
|
31
6-binary/01-arraybuffer-and-views/01-concat/_js.view/test.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
describe("concat", function() {
|
||||||
|
let chunks = [
|
||||||
|
new Uint8Array([0, 1, 2]),
|
||||||
|
new Uint8Array([3, 4, 5]),
|
||||||
|
new Uint8Array([6, 7, 8])
|
||||||
|
];
|
||||||
|
|
||||||
|
it("result has the same array type", function() {
|
||||||
|
|
||||||
|
let result = concat(chunks);
|
||||||
|
|
||||||
|
assert.equal(result.constructor, Uint8Array);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("concatenates arrays", function() {
|
||||||
|
|
||||||
|
let result = concat(chunks);
|
||||||
|
|
||||||
|
assert.deepEqual(result, new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8]));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns empty array on empty input", function() {
|
||||||
|
|
||||||
|
let result = concat([]);
|
||||||
|
|
||||||
|
assert.equal(result.length, 0);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
0
6-binary/01-arraybuffer-and-views/01-concat/solution.md
Normal file
4
6-binary/01-arraybuffer-and-views/01-concat/task.md
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
|
||||||
|
# Concatenate typed arrays
|
||||||
|
|
||||||
|
Given an array of `Uint8Array`, write a function `concat(arrays)` that returns a concatenation of them into a single array.
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 99 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
261
6-binary/01-arraybuffer-and-views/article.md
Normal file
|
@ -0,0 +1,261 @@
|
||||||
|
# ArrayBuffer and views
|
||||||
|
|
||||||
|
Binary data appears when we work with arbitrary files (uploading, downloading, creation). Or when we want to do image/audio processing.
|
||||||
|
|
||||||
|
That's all possible in Javascript, and binary operations are high-performant.
|
||||||
|
|
||||||
|
Although, there's a bit of confusion, because there are many classes. To name a few:
|
||||||
|
- `ArrayBuffer`, `Uint8Array`, `DataView`, etc.
|
||||||
|
|
||||||
|
It may seem complex, but in fact it's not. Everything's fairly simple.
|
||||||
|
|
||||||
|
Let's sort things out.
|
||||||
|
|
||||||
|
**The basic binary object is `ArrayBuffer` -- a fixed-length raw sequence of bytes.**
|
||||||
|
|
||||||
|
We create it like this:
|
||||||
|
```js run
|
||||||
|
let buffer = new ArrayBuffer(16); // create a buffer of length 16
|
||||||
|
alert(buffer.byteLength); // 16
|
||||||
|
```
|
||||||
|
|
||||||
|
This allocates a contiguous memory area of 16 bytes and pre-fills it with zeroes.
|
||||||
|
|
||||||
|
```warn header="`ArrayBuffer` is not `Array` at all"
|
||||||
|
`ArrayBuffer` has nothing in common with `Array`:
|
||||||
|
- It has a fixed length, we can't increase or decrease it.
|
||||||
|
- It takes exactly that much space in the memory.
|
||||||
|
- It can't store any value: only bytes, the exact number of them.
|
||||||
|
```
|
||||||
|
|
||||||
|
`ArrayBuffer` is a raw sequence of bytes. What's stored in it? It has no clue.
|
||||||
|
|
||||||
|
**To manipulate an `ArrayBuffer`, we need to use a "view" object.**
|
||||||
|
|
||||||
|
A view object does not store anything on it's own. It's just an "eyeglasses" that gives an interpretation of the bytes stored in the `ArrayBuffer`.
|
||||||
|
|
||||||
|
For instance:
|
||||||
|
|
||||||
|
- **`Uint8Array`** -- treats each byte as a separate number, with possible values are from 0 to 255 (a byte is 8-bit, so can hold only that much). That's called a "8-bit unsigned integer".
|
||||||
|
- **`Uint16Array`** -- treats every 2 bytes as an integer, with possible values from 0 to 65535. That's called a "16-bit unsigned integer".
|
||||||
|
- **`Uint32Array`** -- treats every 4 bytes as an integer, with possible values from 0 to 4294967295. That's called a "32-bit unsigned integer".
|
||||||
|
- **`Float64Array`** -- treats every 8 bytes as a floating point number with possible values from <code>5.0x10<sup>-324</sup></code> to <code>1.8x10<sup>308</sup></code>.
|
||||||
|
|
||||||
|
So, the binary data in an `ArrayBuffer` of 16 bytes can be interpreted as 16 "tiny numbers", or 8 bigger numbers, or 4 even bigger, or 2 floating-point values with high precision.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
`ArrayBuffer` is the core object, the root of everything, the raw binary data. But if we're going to write into it, or iterate over it, basically for almost any operation – we must use a view, e.g:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let buffer = new ArrayBuffer(16); // create a buffer of length 16
|
||||||
|
|
||||||
|
*!*
|
||||||
|
let view = new Uint32Array(buffer); // treat every 4 bytes as an integer number
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
alert(view.length); // 4, it stores that many numbers
|
||||||
|
alert(view.byteLength); // 16, the length in bytes
|
||||||
|
|
||||||
|
// let's write a value
|
||||||
|
view[0] = 123456;
|
||||||
|
|
||||||
|
// iterate over bytes
|
||||||
|
for(let num of view) {
|
||||||
|
alert(num); // 123456, then 0, 0, 0 (4 numbers total)
|
||||||
|
}
|
||||||
|
|
||||||
|
alert(Uint32Array.BYTES_PER_ELEMENT); // 4 (that many bytes per number)
|
||||||
|
```
|
||||||
|
|
||||||
|
The common term for all these views is [TypedArray](https://tc39.github.io/ecma262/#sec-typedarray-objects). They share the same set of methods and properities.
|
||||||
|
|
||||||
|
They are much more like regular arrays: have indexes and iterable.
|
||||||
|
|
||||||
|
## TypedArray
|
||||||
|
|
||||||
|
A typed array constructor (be it `Int8Array` or `Float64Array`, doesn't matter) behaves differently depending on argument types.
|
||||||
|
|
||||||
|
There are 5 variants:
|
||||||
|
|
||||||
|
```js
|
||||||
|
new TypedArray(buffer, [byteOffset], [length]);
|
||||||
|
new TypedArray(object);
|
||||||
|
new TypedArray(typedArray);
|
||||||
|
new TypedArray(length);
|
||||||
|
new TypedArray();
|
||||||
|
```
|
||||||
|
|
||||||
|
A view cannot exist without an underlying `ArrayBuffer`, so gets created automatically in all these calls except the first one.
|
||||||
|
|
||||||
|
1. If an `ArrayBuffer` argument is supplied, the view is created over it. We used that syntax already.
|
||||||
|
|
||||||
|
Optionally we can provide `byteOffset` to start from (0 by default) and the `length` (till the end of the buffer by default).
|
||||||
|
|
||||||
|
2. If an `Array`, or any array-like object is given, it behaves as `Array.from`: creates a typed array of the same length and copies the content.
|
||||||
|
|
||||||
|
We can use it to pre-fill the array with the data:
|
||||||
|
```js run
|
||||||
|
*!*
|
||||||
|
let arr = new Uint8Array([0, 1, 2, 3]);
|
||||||
|
*/!*
|
||||||
|
alert( arr.length ); // 4
|
||||||
|
alert( arr[1] ); // 1
|
||||||
|
```
|
||||||
|
3. If another `TypedArray` is supplied, it's contents is also copied. Values are converted to the new type in the process. The new array will have the same length.
|
||||||
|
```js run
|
||||||
|
let arr16 = new Uint16Array([1, 1000]);
|
||||||
|
*!*
|
||||||
|
let arr8 = new Uint8Array(arr16);
|
||||||
|
*/!*
|
||||||
|
alert( arr8[0] ); // 1
|
||||||
|
alert( arr8[1] ); // 232 (tried to copy 1000, but can't fit 1000 into 8 bits)
|
||||||
|
```
|
||||||
|
|
||||||
|
4. For a numeric argument `length` -- creates an `ArrayBuffer` to contain that many elements. Its byte length will be `length` multiplied by the number of bytes in a single item `TypedArray.BYTES_PER_ELEMENT`:
|
||||||
|
```js run
|
||||||
|
let arr = new Uint16Array(4);
|
||||||
|
alert( Uint16Array.BYTES_PER_ELEMENT ); // 2 bytes per number
|
||||||
|
alert( arr.byteLength ); // 8 bytes in the buffer, to contain four 2-byte numbers
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Without arguments, creates an zero-length empty `ArrayBuffer`.
|
||||||
|
|
||||||
|
Here's the full list of typed arrays:
|
||||||
|
|
||||||
|
- `Uint8Array`, `Uint16Array`, `Uint32Array` -- for integer numbers of 8, 16 and 32 bits.
|
||||||
|
- `Uint8ClampedArray` -- for 8-bit integers, "clamps" them on assignment (see below).
|
||||||
|
- `Int8Array`, `Int16Array`, `Int32Array` -- for signed integer numbers (can be negative).
|
||||||
|
- `Float32Array`, `Float64Array` -- for signed floating-point numbers of 32 and 64 bits.
|
||||||
|
|
||||||
|
```warn header="No `int8` or similar single-valued types"
|
||||||
|
Please note, despite of the names like `Int8Array`, there's no type like `int8`, or similar in Javascript.
|
||||||
|
|
||||||
|
That's logical, as `Int8Array` is technically not an array, but rather a view on `ArrayBuffer`.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Out-of-bounds behavior
|
||||||
|
|
||||||
|
What if we attempt to write an out-of-bounds value into a typed array? There will be no error. But the result may be not what we want.
|
||||||
|
|
||||||
|
For instance, let's try to put 256 into `Uint8Array`. In binary form, 256 is `100000000` (9 bits), but `Uint8Array` only provides 8 bits per value, that makes the available range from 0 to 255.
|
||||||
|
|
||||||
|
For bigger numbers, only the rightmost (less significant) 8 bits are stored, and the rest is cut off:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
So we'll get zero.
|
||||||
|
|
||||||
|
For 257, the binary form is `100000001` (9 bits), the rightmost 8 get stored, so we'll have `1` in the array:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
In other words, the number modulo 2<sup>8</sup> is saved.
|
||||||
|
|
||||||
|
Here's the demo:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let buffer = new ArrayBuffer(16);
|
||||||
|
let uint8array = new Uint8Array(buffer);
|
||||||
|
|
||||||
|
let num = 256;
|
||||||
|
alert(num.toString(2)); // 100000000 (binary representation)
|
||||||
|
|
||||||
|
uint8array[0] = 256;
|
||||||
|
uint8array[1] = 257;
|
||||||
|
|
||||||
|
alert(uint8array[0]); // 0
|
||||||
|
alert(uint8array[1]); // 1
|
||||||
|
```
|
||||||
|
|
||||||
|
`Uint8ClampedArray` is special in this aspect, its behavior is different. It saves 255 for any number that is greater than 255, and 0 for any negative number. That behavior is sometimes useful for image processing.
|
||||||
|
|
||||||
|
## TypedArray methods
|
||||||
|
|
||||||
|
`TypedArray` has regular `Array` methods, with notable exceptions.
|
||||||
|
|
||||||
|
We can iterate, `map`, `slice`, `find`, `reduce` etc.
|
||||||
|
|
||||||
|
There are few things we can't do though:
|
||||||
|
|
||||||
|
- No `splice` -- we can't "delete" a value, because typed arrays are views on a buffer, and these are fixed, contiguous areas of memory. All we can do is to assign a zero.
|
||||||
|
- No `concat` method.
|
||||||
|
|
||||||
|
There are two additional properties:
|
||||||
|
|
||||||
|
- `arr.buffer` -- the reference to the underlying `ArrayBuffer`.
|
||||||
|
- `arr.byteLength` -- the byte size of the underlying `ArrayBuffer`.
|
||||||
|
|
||||||
|
...And methods:
|
||||||
|
|
||||||
|
- `arr.set(fromArr, [offset])` copies all elements from `fromArr` to the `arr`, starting at position `offset` (0 by default).
|
||||||
|
- `arr.subarray([begin, end])` creates a new view of the same type from `begin` to `end` (exclusive). That's similar to `arr.slice` method, but doesn't copy anything -- just creates a new view.
|
||||||
|
|
||||||
|
These methods allow us to copy typed arrays one onto another, create new arrays from existing ones.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## DataView
|
||||||
|
|
||||||
|
[DataView](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) is a special super-flexible "untyped" view over `ArrayBuffer`.
|
||||||
|
|
||||||
|
It allows to access the data on any offset in any format.
|
||||||
|
|
||||||
|
- For typed arrays, the constructor dictates what the format is, then we access i-th number as `arr[i]`. All array members are assumed to have the same format.
|
||||||
|
- With `DataView` we access the data with methods like `.getUint8(i)`. So we decide what the format is at method call time instead of the construction time.
|
||||||
|
|
||||||
|
The syntax:
|
||||||
|
|
||||||
|
```js
|
||||||
|
new DataView(buffer, [byteOffset], [byteLength])
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`buffer`** -- the underlying `ArrayBuffer`. Unlike typed arrays, `DataView` doesn't create a buffer on its own. We need to have it ready.
|
||||||
|
- **`byteOffset`** -- the starting byte position of the view (by default 0).
|
||||||
|
- **`byteLength`** -- the byte length of the view (by default till the end of `buffer`).
|
||||||
|
|
||||||
|
For instance, here we extract numbers in different formats from the same buffer:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let buffer = new Uint8Array([255, 255, 255, 255]).buffer;
|
||||||
|
|
||||||
|
let dataView = new DataView(buffer);
|
||||||
|
|
||||||
|
// get 8-bit number at offset 0
|
||||||
|
alert( dataView.getUint8(0) ); // 255
|
||||||
|
|
||||||
|
// now get 16-bit number at offset 0, that's 2 bytes, both with max value
|
||||||
|
alert( dataView.getUint16(0) ); // 65535 (biggest 16-bit unsigned int)
|
||||||
|
|
||||||
|
// get 32-bit number at offset 0
|
||||||
|
alert( dataView.getUint32(0) ); // 4294967295 (biggest 32-bit unsigned int)
|
||||||
|
|
||||||
|
dataView.setUint32(0, 0); // set 4-byte number to zero
|
||||||
|
```
|
||||||
|
|
||||||
|
`DataView` is great when we store mixed-format data in the same buffer. E.g 8-bit integers followed by 32-bit floats, or more complex.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
`ArrayBuffer` is the core object, a raw byte sequence.
|
||||||
|
|
||||||
|
To do almost any operation on `ArrayBuffer`, we need a view.
|
||||||
|
|
||||||
|
- It can be a `TypedArray`:
|
||||||
|
- `Uint8Array`, `Uint16Array`, `Uint32Array` -- for integer numbers of 8, 16 and 32 bits.
|
||||||
|
- `Uint8ClampedArray` -- for 8-bit integers, "clamps" them on assignment.
|
||||||
|
- `Int8Array`, `Int16Array`, `Int32Array` -- for signed integer numbers (can be negative).
|
||||||
|
- `Float32Array`, `Float64Array` -- for signed floating-point numbers of 32 and 64 bits.
|
||||||
|
- Or a `DataView` -- the view that uses methods to specify a format, e.g. `getUint8(offset)`.
|
||||||
|
|
||||||
|
In most cases we create and operate directly on typed arrays, leaving `ArrayBuffer` under cover, as a "common discriminator". We can access it as `.buffer` and make another view if needed.
|
||||||
|
|
||||||
|
There are also two additional terms:
|
||||||
|
- `ArrayBufferView` is an umbrella term for all these kinds of views.
|
||||||
|
- `BufferSource` is an umbrella term for `ArrayBuffer` or `ArrayBufferView`.
|
||||||
|
|
||||||
|
These are used in descriptions of methods that operate on binary data. `BufferSource` is one of the most common words, as it means "`ArrayBuffer` or a view over it"
|
||||||
|
|
||||||
|
Here's a cheatsheet:
|
||||||
|
|
||||||
|

|
|
@ -1,13 +0,0 @@
|
||||||
function makeCounter() {
|
|
||||||
let count = 0;
|
|
||||||
|
|
||||||
function counter() {
|
|
||||||
return count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
counter.set = value => count = value;
|
|
||||||
|
|
||||||
counter.decrease = () => count--;
|
|
||||||
|
|
||||||
return counter;
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
function makeCounter() {
|
|
||||||
let count = 0;
|
|
||||||
|
|
||||||
// ... your code ...
|
|
||||||
}
|
|
||||||
|
|
||||||
let counter = makeCounter();
|
|
||||||
|
|
||||||
alert( counter() ); // 0
|
|
||||||
alert( counter() ); // 1
|
|
||||||
|
|
||||||
counter.set(10); // set the new count
|
|
||||||
|
|
||||||
alert( counter() ); // 10
|
|
||||||
|
|
||||||
counter.decrease(); // decrease the count by 1
|
|
||||||
|
|
||||||
alert( counter() ); // 10 (instead of 11)
|
|
|
@ -1,41 +0,0 @@
|
||||||
describe("counter", function() {
|
|
||||||
|
|
||||||
it("increases from call to call", function() {
|
|
||||||
|
|
||||||
let counter = makeCounter();
|
|
||||||
|
|
||||||
assert.equal( counter(), 0 );
|
|
||||||
assert.equal( counter(), 1 );
|
|
||||||
assert.equal( counter(), 2 );
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
describe("counter.set", function() {
|
|
||||||
it("sets the count", function() {
|
|
||||||
|
|
||||||
let counter = makeCounter();
|
|
||||||
|
|
||||||
counter.set(10);
|
|
||||||
|
|
||||||
assert.equal( counter(), 10 );
|
|
||||||
assert.equal( counter(), 11 );
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("counter.decrease", function() {
|
|
||||||
it("decreases the count", function() {
|
|
||||||
|
|
||||||
let counter = makeCounter();
|
|
||||||
|
|
||||||
counter.set(10);
|
|
||||||
|
|
||||||
assert.equal( counter(), 10 );
|
|
||||||
|
|
||||||
counter.decrease();
|
|
||||||
|
|
||||||
assert.equal( counter(), 10 );
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
|
@ -1,13 +0,0 @@
|
||||||
function makeCounter() {
|
|
||||||
let count = 0;
|
|
||||||
|
|
||||||
function counter() {
|
|
||||||
return count++;
|
|
||||||
}
|
|
||||||
|
|
||||||
counter.set = value => count = value;
|
|
||||||
|
|
||||||
counter.decrease = () => count--;
|
|
||||||
|
|
||||||
return counter;
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
function makeCounter() {
|
|
||||||
let count = 0;
|
|
||||||
|
|
||||||
// ... your code ...
|
|
||||||
}
|
|
||||||
|
|
||||||
let counter = makeCounter();
|
|
||||||
|
|
||||||
alert( counter() ); // 0
|
|
||||||
alert( counter() ); // 1
|
|
||||||
|
|
||||||
counter.set(10); // set the new count
|
|
||||||
|
|
||||||
alert( counter() ); // 10
|
|
||||||
|
|
||||||
counter.decrease(); // decrease the count by 1
|
|
||||||
|
|
||||||
alert( counter() ); // 10 (instead of 11)
|
|
|
@ -1,4 +0,0 @@
|
||||||
|
|
||||||
# Concatenate typed arrays
|
|
||||||
|
|
||||||
Given multiple `chunks`, concatenate them into a single typed array.
|
|
|
@ -1,41 +0,0 @@
|
||||||
describe("counter", function() {
|
|
||||||
|
|
||||||
it("increases from call to call", function() {
|
|
||||||
|
|
||||||
let counter = makeCounter();
|
|
||||||
|
|
||||||
assert.equal( counter(), 0 );
|
|
||||||
assert.equal( counter(), 1 );
|
|
||||||
assert.equal( counter(), 2 );
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
describe("counter.set", function() {
|
|
||||||
it("sets the count", function() {
|
|
||||||
|
|
||||||
let counter = makeCounter();
|
|
||||||
|
|
||||||
counter.set(10);
|
|
||||||
|
|
||||||
assert.equal( counter(), 10 );
|
|
||||||
assert.equal( counter(), 11 );
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("counter.decrease", function() {
|
|
||||||
it("decreases the count", function() {
|
|
||||||
|
|
||||||
let counter = makeCounter();
|
|
||||||
|
|
||||||
counter.set(10);
|
|
||||||
|
|
||||||
assert.equal( counter(), 10 );
|
|
||||||
|
|
||||||
counter.decrease();
|
|
||||||
|
|
||||||
assert.equal( counter(), 10 );
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
|
@ -1,129 +0,0 @@
|
||||||
# Binary data
|
|
||||||
|
|
||||||
There's a lot of terms in Javascript, related to binary data: files, images and so on.
|
|
||||||
|
|
||||||
To name a few:
|
|
||||||
- `ArrayBuffer`, `Uint8Array`, `BufferSource`, `DataView`, `Blob`.
|
|
||||||
|
|
||||||
There are many questions how to convert one thing into another, because people just don't spare a minute to understand.
|
|
||||||
|
|
||||||
It may seem complex, but in fact it's not. Everything's fairly simple.
|
|
||||||
|
|
||||||
Let's sort things out.
|
|
||||||
|
|
||||||
**The basic binary entity is `ArrayBuffer` -- a fixed-length raw sequence of bytes.**
|
|
||||||
|
|
||||||
We create it like this:
|
|
||||||
```js run
|
|
||||||
let buffer = new ArrayBuffer(16); // create a buffer of length 16
|
|
||||||
alert(buffer.byteLength); // 16
|
|
||||||
```
|
|
||||||
|
|
||||||
This allocates a contiguous memory area of 16 bytes and pre-fills it with zeroes.
|
|
||||||
|
|
||||||
```warn header="`ArrayBuffer` is not `Array` at all"
|
|
||||||
Please note: technically `ArrayBuffer` has nothing in common with `Array`:
|
|
||||||
- It has a fixed length, we can't increase or decrease it.
|
|
||||||
- It takes exactly that much space in the memory.
|
|
||||||
- It can only store bytes, the exact number of them.
|
|
||||||
```
|
|
||||||
|
|
||||||
**To manipulate an `ArrayBuffer`, we need to use a "view" object.**
|
|
||||||
|
|
||||||
A view object does not store anything on it's own. It's just an "eyeglasses" that give different representation of the bytes.
|
|
||||||
|
|
||||||
For instance:
|
|
||||||
|
|
||||||
- **`Uint8Array`** -- treats each byte as a separate number, with possible values are from 0 to 255 (a byte is 8-bit, so can hold only that much). That's called a "8-bit unsigned integer".
|
|
||||||
- **`Uint16Array`** -- treats every 2 bytes as an integer, with possible values from 0 to 65535. That's called a "16-bit unsigned integer".
|
|
||||||
- **`Uint32Array`** -- treats every 4 bytes as an integer, with possible values from 0 to 4294967295. That's called a "32-bit unsigned integer".
|
|
||||||
- **`Float64Array`** -- treats every 8 bytes as a floating point number with possible values from <code>5.0x10<sup>-324</sup></code> to <code>1.8x10<sup>308</sup></code>.
|
|
||||||
|
|
||||||
So, an `ArrayBuffer` of 16 bytes can be interpreted as 16 "tiny numbers", or 8 bigger numbers, or 4 even bigger, or 2 floating-point values with high precision.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
`ArrayBuffer` is the core object, the root of everything, the raw binary data. But if we're going to write into it, or iterate over it, basically for almost any operation – we must use a view, e.g:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let buffer = new ArrayBuffer(16); // create a buffer of length 16
|
|
||||||
|
|
||||||
let view = new Uint32Array(buffer); // treat every 4 bytes as an integer number
|
|
||||||
|
|
||||||
alert(view.length); // 4, it stores that many numbers
|
|
||||||
alert(view.byteLength); // 16, the length in bytes
|
|
||||||
|
|
||||||
// let's write a value
|
|
||||||
view[0] = 123456;
|
|
||||||
|
|
||||||
for(let num of view) {
|
|
||||||
alert(num); // 123456, then 0, 0, 0 (4 numbers total)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
These numeric views are called "Typed Arrays".
|
|
||||||
|
|
||||||
## TypedArray
|
|
||||||
|
|
||||||
Here's a full list of typed arrays:
|
|
||||||
|
|
||||||
- `Uint8Array`, `Uint16Array`, `Uint32Array` -- for integer numbers of 8, 16 and 32 bits.
|
|
||||||
- `Uint8ClampedArray` -- for 8-bit integers, "clamps" them on assignment (see below).
|
|
||||||
- `Int8Array`, `Int16Array`, `Int32Array` -- for signed integer numbers (can be negative).
|
|
||||||
- `Float32Array`, `Float64Array` -- for signed floating-point numbers of 32 and 64 bits.
|
|
||||||
|
|
||||||
`Uint8ClampedArray` has a special feature. Usually, when we write a number that doesn't fit, into an integer typed array, then its most significant bytes are cut-off.
|
|
||||||
|
|
||||||
For instance, let's try to write 256 into `Uint8Array`. In binary form, 256 is `100000000` (9 bits), but `Uint8Array` only provides 8 per value.
|
|
||||||
|
|
||||||
So only the rightmost 8 bits are kept, and the rest is cut off:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
So we get zero. Here's the demo:
|
|
||||||
|
|
||||||
```js run
|
|
||||||
let buffer = new ArrayBuffer(16);
|
|
||||||
let uint8array = new Uint8Array(buffer);
|
|
||||||
|
|
||||||
let num = 256;
|
|
||||||
alert(num.toString(2)); // 100000000 (binary representation)
|
|
||||||
|
|
||||||
uint8array[0] = num;
|
|
||||||
|
|
||||||
alert(uint8array[0]); // 0
|
|
||||||
```
|
|
||||||
|
|
||||||
For 257, the binary form is `100000001` (9 bits), so we'll have `1` in the array:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
In other words, the number modulo 2<sup>8</sup> is saved.
|
|
||||||
|
|
||||||
For `Uint8ClampedArray` the behavior is different. It saves 255 for any number that is greater than 255, and 0 for any negative number. That behavior is useful in image processing.
|
|
||||||
|
|
||||||
**`TypedArray` methods are similar to regular Javascript arrays, with notable exceptions.**
|
|
||||||
|
|
||||||
We can itereate, `map`, `slice`, `find`, etc.
|
|
||||||
|
|
||||||
There are few things we can't do though:
|
|
||||||
|
|
||||||
- No `splice` -- we can't "delete" a value, because typed arrays are views on a buffer, and these are contiguous areas of memory. All we can do is to assign a zero.
|
|
||||||
- No `concat` method, we can use `set` instead.
|
|
||||||
- The `set(fromArr, offset)` method copies all elements from `fromArr` to the current array, starting at position `offset`.
|
|
||||||
|
|
||||||
To concatenate several typed arrays, create a new one with the combined length and `set` onto it:
|
|
||||||
|
|
||||||
// Step 4: join chunks into result
|
|
||||||
let chunksAll = new Uint8Array(receivedLength); // (4.1)
|
|
||||||
let position = 0;
|
|
||||||
for(let chunk of chunks) {
|
|
||||||
chunksAll.set(chunk, position); // (4.2)
|
|
||||||
position += chunk.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
The full list of `ArrayBufferViews`:
|
|
||||||
|
|
||||||
- `Int8Array`
|
|
76
6-binary/02-text-decoder/article.md
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
# TextDecoder, TextEncoder
|
||||||
|
|
||||||
|
What if the binary data is actually a string?
|
||||||
|
|
||||||
|
The build-in [TextDecoder](https://encoding.spec.whatwg.org/#interface-textdecoder) object allows to read the value into an an actual Javascript string, given the buffer and the encoding.
|
||||||
|
|
||||||
|
We first need to create it:
|
||||||
|
```js
|
||||||
|
let decoder = new TextDecoder([label], [options]);
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`label`** -- the encoding, `utf-8` by default.
|
||||||
|
- **`options`** -- optional object:
|
||||||
|
- **`fatal`** -- boolean, if `true` then throw an exception for invalid string, otherwise (default) replace invalid byte sequences with character `\uFFFD`.
|
||||||
|
- **`ignoreBOM`** -- boolean, if `true` then ignore BOM (an optional byte-order unicode mark), we usually don't set it.
|
||||||
|
|
||||||
|
...And then decode:
|
||||||
|
|
||||||
|
```js
|
||||||
|
let str = decoder.decode([input], [options]);
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`input`** -- `BufferSource` to decode.
|
||||||
|
- **`options`** -- optional object:
|
||||||
|
- **`stream`** -- true for decoding streams, when `decoder` is called repeatedly with incoming chunks of data. In that case a multi-byte character may occasionally split between chunks. This options tells `TextDecoder` to memorize "unfinished" characters and decode them when the next chunk comes.
|
||||||
|
|
||||||
|
For instance:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let uint8Array = new Uint8Array([72, 101, 108, 108, 111]);
|
||||||
|
|
||||||
|
alert( new TextDecoder().decode(uint8Array) ); // Hello
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let uint8Array = new Uint8Array([228, 189, 160, 229, 165, 189]);
|
||||||
|
|
||||||
|
alert( new TextDecoder().decode(uint8Array) ); // 你好
|
||||||
|
```
|
||||||
|
|
||||||
|
We can decode a part of the buffer by passing it a subarray:
|
||||||
|
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let uint8Array = new Uint8Array([0, 72, 101, 108, 108, 111, 0]);
|
||||||
|
|
||||||
|
// the string is in the middle
|
||||||
|
// create a new view over it, without copying anything
|
||||||
|
let binaryString = uint8Array.subarray(1, -1);
|
||||||
|
|
||||||
|
alert( new TextDecoder().decode(binaryString) ); // Hello
|
||||||
|
```
|
||||||
|
|
||||||
|
## TextEncoder
|
||||||
|
|
||||||
|
[TextEncoder](https://encoding.spec.whatwg.org/#interface-textencoder) does the reverse thing -- writes a string into a buffer.
|
||||||
|
|
||||||
|
The syntax is:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let encoder = new TextEncoder();
|
||||||
|
```
|
||||||
|
|
||||||
|
The only encoding it supports is "utf-8".
|
||||||
|
|
||||||
|
It has two methods:
|
||||||
|
- **`encode(str)`** -- returns `Uint8Array` from a string.
|
||||||
|
- **`encodeInto(str, destination)`** -- encodes `str` into `destination` that must be `Uint8Array`.
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let encoder = new TextEncoder();
|
||||||
|
|
||||||
|
let uint8Array = encoder.encode("Hello");
|
||||||
|
alert(uint8Array); // 72,101,108,108,111
|
||||||
|
```
|
212
6-binary/03-blob/article.md
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
# Blob
|
||||||
|
|
||||||
|
`ArrayBuffer` and views are a part of ECMA standard, a part of Javascript.
|
||||||
|
|
||||||
|
In the browser, there are additional higher-level objects, described in [File API](https://www.w3.org/TR/FileAPI/).
|
||||||
|
|
||||||
|
`Blob` consists of an optional string `type` (a MIME-type usually), plus `blobParts` this a sequence of other `Blob` objects, strings and `BufferSources`.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Thanks to `type`, we can download/upload blobs, and it naturally becomes `Content-Type` in network requests.
|
||||||
|
|
||||||
|
The constructor syntax is:
|
||||||
|
|
||||||
|
```js
|
||||||
|
new Blob(blobParts, options);
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`blobParts`** is an array of Blob/BufferSource/String values.
|
||||||
|
- **`options`** optional object:
|
||||||
|
- **`type`** -- blob type, usually MIME-type, e.g. `image/png`,
|
||||||
|
- **`endings`** -- whether to transform end-of-line `\r\n` to `\n` end vise versa, to make the blob correspond to current OS newlines. By default `"transparent"` (do nothing), but also can be `"native"` (transform).
|
||||||
|
|
||||||
|
Then we can access blob as a whole, or extract slices with:
|
||||||
|
|
||||||
|
```js
|
||||||
|
blob.slice([byteStart], [byteEnd], [contentType]);
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`byteStart`** -- the starting byte, by default 0.
|
||||||
|
- **`byteEnd`** -- the last byte (exclusive, by default till the end).
|
||||||
|
- **`contentType`** -- the `type` of the new blob, by default the same as the source.
|
||||||
|
|
||||||
|
The arguments are similar to `array.slice`, negative numbers are allowed too.
|
||||||
|
|
||||||
|
We can't change data directly in a blob, but we can copy parts of blobs, create new blobs from them, mix them and so on. This behavior is similar to Javascript strings: we can't change a character in a string, but we can make a new corrected string.
|
||||||
|
|
||||||
|
## Blob as URL
|
||||||
|
|
||||||
|
A Blob can be easily used in an URL for `<a>`, `<img>` or other tags.
|
||||||
|
|
||||||
|
Let's start with a simple example. By clicking on a link you download a dynamically-generated blob with `hello world` contents as a file:
|
||||||
|
|
||||||
|
```html run
|
||||||
|
<!-- download attribute forces the browser to download instead of navigating -->
|
||||||
|
<a download="hello.txt" href='#' id="link">Download</a>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let binary = new Uint8Array([72, 101, 108, 108, 111]); // "hello" in binary form
|
||||||
|
|
||||||
|
// create blob of multiple parts
|
||||||
|
let blob = new Blob([binary, ' ', 'world'], {type: 'text/plain'});
|
||||||
|
|
||||||
|
link.href = URL.createObjectURL(blob);
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
We can also create a link in Javascript and simulate a click by `link.click()`, then download starts authomatically.
|
||||||
|
|
||||||
|
Here's the similar "on the fly" blob creation and download code, now without HTML:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let link = document.createElement('a');
|
||||||
|
link.download = 'hello.txt';
|
||||||
|
|
||||||
|
let blob = new Blob(['Hello, world!'], {type: 'text/plain'});
|
||||||
|
|
||||||
|
link.href = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
URL.revokeObjectURL(link.href);
|
||||||
|
```
|
||||||
|
|
||||||
|
**`URL.createObjectURL` takes a blob and creates an unique URL for it, in the form `blob:<origin>/<uuid>`.**
|
||||||
|
|
||||||
|
That's what the generated url looks like:
|
||||||
|
|
||||||
|
```
|
||||||
|
blob:https://javascript.info/1e67e00e-860d-40a5-89ae-6ab0cbee6273
|
||||||
|
```
|
||||||
|
|
||||||
|
The browser keeps an internal url -> blob mapping. For each url generated by `URL.createObjectURL`, it stores a reference to the corresponding blob.
|
||||||
|
|
||||||
|
A generated url is only valid while the current document is open. And it allows to reference the blob in `<img>`, `<a>`, any other object that expects an URL.
|
||||||
|
|
||||||
|
There's a side-effect though. While there's an mapping for a blob, the blob itself resides in the memory. The browser can't free it.
|
||||||
|
|
||||||
|
The mapping is automatically cleared on document unload, so blobs are freed then. But if an app is long-living, then that doesn't happen. So if we create an URL, that blob will hang in memory, even if not needed any more.
|
||||||
|
|
||||||
|
**`URL.revokeObjectURL(url)` removes the reference from the internal mapping, thus allowing the blob to be deleted (if there are no other references), and the memory to be freed.**
|
||||||
|
|
||||||
|
In the last example, we intend the blob to be used only once, for instant downloading, so we call `URL.revokeObjectURL(link.href)` immediately.
|
||||||
|
|
||||||
|
In the previous example though, with the clickable HTML-link, we dont't call `URL.revokeObjectURL(link.href)`, because that would make the blob url invalid. After the revocation, as the mapping is removed, the url doesn't work any more.
|
||||||
|
|
||||||
|
## Blob to base64
|
||||||
|
|
||||||
|
An alternative to `URL.createObjectURL` is to convert a blob into a base64-encoded string.
|
||||||
|
|
||||||
|
That encoding represents binary data as a string of ultra-safe "readable" characters with ASCII-codes from 0 to 64. And what's more important -- we can use this encoding in "data-urls".
|
||||||
|
|
||||||
|
A [data url](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) has the form `data:[<mediatype>][;base64],<data>`. We can use such urls everywhere, on par with "regular" urls.
|
||||||
|
|
||||||
|
For instance, here's a smiley:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<img src="data:image/png;base64,R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7">
|
||||||
|
```
|
||||||
|
|
||||||
|
The browser will decode the string and show the image: <img src="data:image/png;base64,R0lGODlhDAAMAKIFAF5LAP/zxAAAANyuAP/gaP///wAAAAAAACH5BAEAAAUALAAAAAAMAAwAAAMlWLPcGjDKFYi9lxKBOaGcF35DhWHamZUW0K4mAbiwWtuf0uxFAgA7">
|
||||||
|
|
||||||
|
|
||||||
|
To transform a blob into base64 and vise-versa, we'll use built-in `FileReader/FileWriter` objects. In the [next chapter](info:file) we'll cover those more in-detail. For now, it's important only that they can read a blob as data url, that we can assign it to `link.href`.
|
||||||
|
|
||||||
|
Here's the demo of downloading a blob, now via base-64:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let link = document.createElement('a');
|
||||||
|
link.download = 'hello.txt';
|
||||||
|
|
||||||
|
let blob = new Blob(['Hello, world!'], {type: 'text/plain'});
|
||||||
|
|
||||||
|
*!*
|
||||||
|
let reader = new FileReader();
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
reader.onload = function() {
|
||||||
|
link.href = reader.result; // data url
|
||||||
|
link.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
*!*
|
||||||
|
reader.readAsDataURL(blob); // converts the blob to base64 and calls onload
|
||||||
|
*/!*
|
||||||
|
```
|
||||||
|
|
||||||
|
Both ways of making an URL of a blob are usable. But usually `URL.createObjectURL(blob)` is simpler and faster.
|
||||||
|
|
||||||
|
```compare title-plus="URL.createObjectURL(blob)" title-minus="Blob to data url"
|
||||||
|
+ We need to revoke them if care about memory.
|
||||||
|
+ Direct access to blob, no "encoding/decoding"
|
||||||
|
- No need to revoke anything.
|
||||||
|
- Performance and memory losses on big blobs for encoding.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Image or a page to blob
|
||||||
|
|
||||||
|
We can create a blob of an image, or even a page screenshot.
|
||||||
|
|
||||||
|
That's usually done via `<canvas>` element:
|
||||||
|
|
||||||
|
1. We draw an existing image on canvas using [canvas.drawImage](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage).
|
||||||
|
2. Canvas [.toBlob(callback, format, quality)](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob) creates a blob and calls `callback` with it.
|
||||||
|
|
||||||
|
In the example below, an image is just copied, but we could cut from it, or transform it on canvas prior to making a blob:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let img = document.querySelector('img'); // just any image
|
||||||
|
|
||||||
|
let canvas = document.createElement('canvas');
|
||||||
|
canvas.width = img.clientWidth;
|
||||||
|
canvas.height = img.clientHeight;
|
||||||
|
|
||||||
|
let context = canvas.getContext('2d');
|
||||||
|
|
||||||
|
context.drawImage(img, 0, 0); // allows to cut image
|
||||||
|
// we can context.rotate() as well, and do many other things
|
||||||
|
|
||||||
|
canvas.toBlob(function(blob) {
|
||||||
|
// download the blob
|
||||||
|
let link = document.createElement('a');
|
||||||
|
link.download = 'example.png';
|
||||||
|
link.href = URL.createObjectURL(blob);
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
URL.revokeObjectURL(link.href);
|
||||||
|
}, 'image/png');
|
||||||
|
```
|
||||||
|
|
||||||
|
For screenshotting a page, we can use a library such as <https://github.com/niklasvh/html2canvas> to draw the screenshot on `<canvas>`, and then download it the same way.
|
||||||
|
|
||||||
|
## From Blob to ArrayBuffer
|
||||||
|
|
||||||
|
The `Blob` constructor allows to create a blob from almost anything.
|
||||||
|
|
||||||
|
But we can also revert back to lowest-level `ArrayBuffer` using `FileReader`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// get arrayBuffer from blob
|
||||||
|
let fileReader = new FileReader();
|
||||||
|
fileReader.onload = function(event) {
|
||||||
|
let arrayBuffer = fileReader.result;
|
||||||
|
};
|
||||||
|
*!*
|
||||||
|
fileReader.readAsArrayBuffer(blob);
|
||||||
|
*/!*
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
While `ArrayBuffer`, `Uint8Array` and other `BufferSource` are "binary data", a [Blob](https://www.w3.org/TR/FileAPI/#dfn-Blob) represents "binary data with type".
|
||||||
|
|
||||||
|
That makes Blobs convenient for upload/download operations, that are so common in the browser.
|
||||||
|
|
||||||
|
Methods that perform web-requests, such as [XMLHttpRequest](info:xmlhttprequest), [fetch](info:fetch) and so on, can work with `Blob` natively, as well as with other binary types.
|
||||||
|
|
||||||
|
That said, it's still possible to convert betweeen `Blob` and low-level binary data types:
|
||||||
|
|
||||||
|
- We can make a Blob from a typed array using `new Blob(...)` constructor.
|
||||||
|
- We can get back `ArrayBuffer` from a Blob using `FileReader`, and then create a view over it for low-level binary processing.
|
BIN
6-binary/03-blob/blob.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
6-binary/03-blob/blob@2x.png
Normal file
After Width: | Height: | Size: 23 KiB |
106
6-binary/04-file/article.md
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
# File and FileReader
|
||||||
|
|
||||||
|
A [File](https://www.w3.org/TR/FileAPI/#dfn-file) object inhereits from `Blob`, but is extended with filesystem-related capabilities.
|
||||||
|
|
||||||
|
There are two ways to obtain it.
|
||||||
|
|
||||||
|
First, there's a constructor, similar to `Blob`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
new File(fileParts, fileName, [options])
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`fileParts`** -- is an array of Blob/BufferSource/String value, same as `Blob`.
|
||||||
|
- **`fileName`** -- file name string.
|
||||||
|
- **`options`** -- optional object:
|
||||||
|
- **`lastModified`** -- a timestamp (integer date) of last modification.
|
||||||
|
|
||||||
|
Second, more often we get a file from `<input type="file">` or drag'n'drop or other browser interfaces. Then the file gets these from OS.
|
||||||
|
|
||||||
|
For instance:
|
||||||
|
|
||||||
|
```html run
|
||||||
|
<input type="file" onchange="showFile(this)">
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function showFile(input) {
|
||||||
|
let file = input.files[0];
|
||||||
|
|
||||||
|
alert(`File name: ${file.name}`); // e.g my.png
|
||||||
|
alert(`Last modified: ${file.lastModified}`); // e.g 1552830408824
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
```smart
|
||||||
|
Please note: the input may select multiple files, so `input.files` is an array-like object with them. Here we have only one file, but it's still the same, so we just take `input.files[0]`.
|
||||||
|
```
|
||||||
|
|
||||||
|
## FileReader
|
||||||
|
|
||||||
|
[FileReader](https://www.w3.org/TR/FileAPI/#dfn-filereader) is an object with the sole purpose of reading from `Blob` (and hence `File` too) objects.
|
||||||
|
|
||||||
|
It's event based, as reading from disk may take time.
|
||||||
|
|
||||||
|
The constructor:
|
||||||
|
|
||||||
|
```js
|
||||||
|
let reader = new FileReader(); // no arguments
|
||||||
|
```
|
||||||
|
|
||||||
|
The main methods:
|
||||||
|
|
||||||
|
- **`readAsArrayBuffer(blob)`** -- read the data as `ArrayBuffer`
|
||||||
|
- **`readAsText(blob, [encoding])`** -- read the data as a string (encoding is `utf-8` by default)
|
||||||
|
- **`readAsDataURL(blob)`** -- encode the data as base64 data url.
|
||||||
|
- **`abort()`** -- cancel the operation.
|
||||||
|
|
||||||
|
As the reading proceeds, there are events:
|
||||||
|
- `loadstart` -- loading started.
|
||||||
|
- `progress` -- occurs during reading.
|
||||||
|
- `load` -- no errors, reading complete.
|
||||||
|
- `abort` -- `abort()` called.
|
||||||
|
- `error` -- error has occured.
|
||||||
|
- `loadend` -- reading finished with either success or failure.
|
||||||
|
|
||||||
|
At the end:
|
||||||
|
- `reader.result` is the result (if successful)
|
||||||
|
- `reader.error` is the error (if failed).
|
||||||
|
|
||||||
|
The most widely used events are for sure `load` and `error`.
|
||||||
|
|
||||||
|
Here's an example:
|
||||||
|
|
||||||
|
```html run
|
||||||
|
<input type="file" onchange="readFile(this)">
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function readFile(input) {
|
||||||
|
let file = input.files[0];
|
||||||
|
|
||||||
|
let reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = function() {
|
||||||
|
console.log(reader.result);
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.onerror = function() {
|
||||||
|
console.log(reader.error);
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsText(file);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
```smart header="`FileReaderSync` is available for workers only"
|
||||||
|
For Web Workers, there also exists a synchronous variant of `FileReader`, called [FileReaderSync](https://www.w3.org/TR/FileAPI/#FileReaderSync).
|
||||||
|
|
||||||
|
Reading methods `read*` do not generate events, but rather return the result, as regular functions do.
|
||||||
|
|
||||||
|
That's only inside a Web Worker though, because delays and hang-ups in Web Workers are less important, they do not affect the page.
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
It most often used to read from files, and
|
BIN
6-binary/04-file/blob.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
6-binary/04-file/blob@2x.png
Normal file
After Width: | Height: | Size: 23 KiB |
|
@ -60,7 +60,7 @@ To do the request, we need 3 steps:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
xhr.onload = function() {
|
xhr.onload = function() {
|
||||||
alert(`Loaded: ${xhr.status} ${xhr.responseText}`);
|
alert(`Loaded: ${xhr.status} ${xhr.response}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.onerror = function() { // only triggers if the request couldn't be made at all
|
xhr.onerror = function() { // only triggers if the request couldn't be made at all
|
||||||
|
@ -69,7 +69,8 @@ To do the request, we need 3 steps:
|
||||||
|
|
||||||
xhr.onprogress = function(event) { // triggers periodically
|
xhr.onprogress = function(event) { // triggers periodically
|
||||||
// event.loaded - how many bytes downloaded
|
// event.loaded - how many bytes downloaded
|
||||||
// event.total - total number of bytes (if server set Content-Length header)
|
// event.lengthComputable = true if the server sent Content-Length header
|
||||||
|
// event.total - total number of bytes (if lengthComputable)
|
||||||
alert(`Received ${event.loaded} of ${event.total}`);
|
alert(`Received ${event.loaded} of ${event.total}`);
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
@ -91,13 +92,17 @@ xhr.onload = function() {
|
||||||
if (xhr.status != 200) { // analyze HTTP status of the response
|
if (xhr.status != 200) { // analyze HTTP status of the response
|
||||||
alert(`Error ${xhr.status}: ${xhr.statusText}`); // e.g. 404: Not Found
|
alert(`Error ${xhr.status}: ${xhr.statusText}`); // e.g. 404: Not Found
|
||||||
} else { // show the result
|
} else { // show the result
|
||||||
alert(`Done, got ${xhr.responseText.length} bytes`); // responseText is the server
|
alert(`Done, got ${xhr.response.length} bytes`); // responseText is the server
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.onprogress = function(event) {
|
xhr.onprogress = function(event) {
|
||||||
// event.total=0 if the server did not send Content-Length header
|
if (event.lengthComputable) {
|
||||||
alert(`Received ${event.loaded} of ${event.total}`);
|
alert(`Received ${event.loaded} of ${event.total} bytes`);
|
||||||
|
} else {
|
||||||
|
alert(`Received ${event.loaded} bytes`); // no Content-Length
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.onerror = function() {
|
xhr.onerror = function() {
|
||||||
|
@ -113,10 +118,8 @@ Once the server has responded, we can receive the result in the following proper
|
||||||
`statusText`
|
`statusText`
|
||||||
: HTTP status message (a string): usually `OK` for `200`, `Not Found` for `404`, `Forbidden` for `403` and so on.
|
: HTTP status message (a string): usually `OK` for `200`, `Not Found` for `404`, `Forbidden` for `403` and so on.
|
||||||
|
|
||||||
`responseText`
|
`response` (old scripts may use `responseText`)
|
||||||
: The text of the server response, if JSON, then we can `JSON.parse` it.s
|
: The server response.
|
||||||
|
|
||||||
If the server returns XML with the correct header `Content-type: text/xml`, then there's also `responseXML` property with the parsed XML document. We can query it with `xhr.responseXml.querySelector("...")` and perform other XML-specific operations.
|
|
||||||
|
|
||||||
If we changed our mind, we can terminate the request at any time. The call to `xhr.abort()` does that:
|
If we changed our mind, we can terminate the request at any time. The call to `xhr.abort()` does that:
|
||||||
|
|
||||||
|
@ -134,8 +137,46 @@ xhr.timeout = 10000; // timeout in ms, 10 seconds
|
||||||
|
|
||||||
If the request does not succeed within the given time, it gets canceled and `timeout` event triggers.
|
If the request does not succeed within the given time, it gets canceled and `timeout` event triggers.
|
||||||
|
|
||||||
|
## Response Type
|
||||||
|
|
||||||
### Ready states
|
We can use `xhr.responseType` property to set the response format:
|
||||||
|
|
||||||
|
- `""` (default) -- get as string,
|
||||||
|
- `"text"` -- get as string,
|
||||||
|
- `"arraybuffer"` -- get as `ArrayBuffer` (for binary data, see chapter <info:arraybuffer-and-views>),
|
||||||
|
- `"blob"` -- get as `Blob` (for binary data, see chapter <info:blob>),
|
||||||
|
- `"document"` -- get as XML document (can use XPath and other XML methods),
|
||||||
|
- `"json"` -- get as JSON (parsed automatically).
|
||||||
|
|
||||||
|
For example, let's get the response as JSON:
|
||||||
|
|
||||||
|
```js run
|
||||||
|
let xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
|
xhr.open('GET', '/article/xmlhttprequest/example/json');
|
||||||
|
|
||||||
|
*!*
|
||||||
|
xhr.responseType = 'json';
|
||||||
|
*/!*
|
||||||
|
|
||||||
|
xhr.send();
|
||||||
|
|
||||||
|
// the response is {"message": "Hello, world!"}
|
||||||
|
xhr.onload = function() {
|
||||||
|
let responseObj = xhr.response;
|
||||||
|
alert(responseObj.message); // Hello, world!
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
```smart
|
||||||
|
In the old scripts you may also find `xhr.responseText` and even `xhr.responseXML` properties.
|
||||||
|
|
||||||
|
They exist for historical reasons, to get either a string or XML document. Nowadays, we should set the format in `xhr.responseType` and get `xhr.response` as demonstrated above.
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Ready states
|
||||||
|
|
||||||
`XMLHttpRequest` changes between states as it progresses. The current state is accessible as `xhr.readyState`.
|
`XMLHttpRequest` changes between states as it progresses. The current state is accessible as `xhr.readyState`.
|
||||||
|
|
||||||
|
@ -186,7 +227,7 @@ try {
|
||||||
if (xhr.status != 200) {
|
if (xhr.status != 200) {
|
||||||
alert(`Error ${xhr.status}: ${xhr.statusText}`);
|
alert(`Error ${xhr.status}: ${xhr.statusText}`);
|
||||||
} else {
|
} else {
|
||||||
alert(xhr.responseText);
|
alert(xhr.response);
|
||||||
}
|
}
|
||||||
} catch(err) { // instead of onerror
|
} catch(err) { // instead of onerror
|
||||||
alert("Request failed");
|
alert("Request failed");
|
||||||
|
@ -275,7 +316,7 @@ There are 3 methods for HTTP-headers:
|
||||||
}, {});
|
}, {});
|
||||||
```
|
```
|
||||||
|
|
||||||
## POST requests
|
## POST, FormData
|
||||||
|
|
||||||
To make a POST request, we can use the built-in [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData) object.
|
To make a POST request, we can use the built-in [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData) object.
|
||||||
|
|
||||||
|
@ -293,7 +334,7 @@ Create it, optionally from a form, `append` more fields if needed, and then:
|
||||||
|
|
||||||
For instance:
|
For instance:
|
||||||
|
|
||||||
```html
|
```html run
|
||||||
<form name="person">
|
<form name="person">
|
||||||
<input name="name" value="John">
|
<input name="name" value="John">
|
||||||
<input name="surname" value="Smith">
|
<input name="surname" value="Smith">
|
||||||
|
@ -308,8 +349,10 @@ For instance:
|
||||||
|
|
||||||
// send it out
|
// send it out
|
||||||
let xhr = new XMLHttpRequest();
|
let xhr = new XMLHttpRequest();
|
||||||
xhr.open("POST", "/url");
|
xhr.open("POST", "/article/xmlhttprequest/post/user");
|
||||||
xhr.send(formData);
|
xhr.send(formData);
|
||||||
|
|
||||||
|
xhr.
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -333,7 +376,7 @@ xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8');
|
||||||
xhr.send(json);
|
xhr.send(json);
|
||||||
```
|
```
|
||||||
|
|
||||||
`XMLHttpRequest` can also send binary data using Blob and similar objects. We'll cover binary data later, in the chapter about `fetch`.
|
The `.send(body)` method is pretty omnivore. It can send almost everything, including Blob and BufferSource objects.
|
||||||
|
|
||||||
## Tracking upload progress
|
## Tracking upload progress
|
||||||
|
|
||||||
|
@ -371,6 +414,7 @@ xhr.upload.onerror = function() {
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
Typical code of the GET-request with `XMLHttpRequest`:
|
Typical code of the GET-request with `XMLHttpRequest`:
|
||||||
|
@ -389,7 +433,7 @@ xhr.onload = function() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the response from xhr.responseText
|
// get the response from xhr.response
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.onprogress = function(event) {
|
xhr.onprogress = function(event) {
|
||||||
|
|
|
@ -28,6 +28,14 @@ function accept(req, res) {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
} else if (req.url == '/json') {
|
||||||
|
res.writeHead(200, {
|
||||||
|
// 'Content-Type': 'application/json;charset=utf-8',
|
||||||
|
'Cache-Control': 'no-cache'
|
||||||
|
});
|
||||||
|
|
||||||
|
res.write(JSON.stringify({message: "Hello, world!"}));
|
||||||
|
res.end();
|
||||||
} else {
|
} else {
|
||||||
file.serve(req, res);
|
file.serve(req, res);
|
||||||
}
|
}
|
||||||
|
|
36
7-network/10-xmlhttprequest/post.view/index.html
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<!doctype html>
|
||||||
|
<script>
|
||||||
|
(async () {
|
||||||
|
|
||||||
|
const response = await fetch('long.txt');
|
||||||
|
const reader = response.body.getReader();
|
||||||
|
|
||||||
|
const contentLength = +response.headers.get('Content-Length');
|
||||||
|
let receivedLength = 0;
|
||||||
|
let chunks = [];
|
||||||
|
while(true) {
|
||||||
|
const chunk = await reader.read();
|
||||||
|
|
||||||
|
if (chunk.done) {
|
||||||
|
console.log("done!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
chunks.push(chunk.value);
|
||||||
|
receivedLength += chunk.value.length;
|
||||||
|
console.log(`${receivedLength}/${contentLength} received`)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let chunksMerged = new Uint8Array(receivedLength);
|
||||||
|
let length = 0;
|
||||||
|
for(let chunk of chunks) {
|
||||||
|
chunksMerged.set(chunk, length);
|
||||||
|
length += chunk.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = new TextDecoder("utf-8").decode(chunksMerged);
|
||||||
|
console.log(result);
|
||||||
|
})();
|
||||||
|
|
||||||
|
</script>
|
55
7-network/10-xmlhttprequest/post.view/server.js
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
let http = require('http');
|
||||||
|
let url = require('url');
|
||||||
|
let querystring = require('querystring');
|
||||||
|
let static = require('node-static');
|
||||||
|
let file = new static.Server('.', {
|
||||||
|
cache: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function accept(req, res) {
|
||||||
|
|
||||||
|
if (req.method == 'POST') {
|
||||||
|
let chunks = [];
|
||||||
|
let length = 0;
|
||||||
|
|
||||||
|
req.on('data', function (data) {
|
||||||
|
chunks.push(data);
|
||||||
|
length += data.length;
|
||||||
|
|
||||||
|
// Too much POST data, kill the connection!
|
||||||
|
if (length > 1e6) {
|
||||||
|
request.connection.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on('end', function() {
|
||||||
|
// let post = JSON.parse(chunks.join(''));
|
||||||
|
|
||||||
|
if (req.url == '/user') {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ message: 'User saved' }));
|
||||||
|
} else if (req.url == '/image') {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ message: "Image saved", imageSize: length }));
|
||||||
|
} else {
|
||||||
|
res.writeHead(404);
|
||||||
|
res.end("Not found");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
} else {
|
||||||
|
file.serve(req, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ------ запустить сервер -------
|
||||||
|
|
||||||
|
if (!module.parent) {
|
||||||
|
http.createServer(accept).listen(8080);
|
||||||
|
} else {
|
||||||
|
exports.accept = accept;
|
||||||
|
}
|