Mock ReadableStream - javascript

Consider following code:
fetch("/").then(response => {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let res = 0;
return reader.read().then(function processResult(result) {
if (result.done) {
return res;
}
const part = decoder.decode(result.value, { stream: true });
res += part.length;
return reader.read().then(processResult);
});
}).then(res => console.log(res));
Now I want to test it. I'm mocking fetch to return fake response that should provide some reader. I want that reader to return 2 portions of data (see pieces array):
import { stub } from "sinon";
const pieces = [
new Uint8Array([65, 98, 99, 32, 208]), // "Abc " and first byte of "й"
new Uint8Array([185, 209, 139, 209, 141]), // Second byte of "й" and "ыэ"
];
const fetchStub = stub(window, "fetch");
fetchStub.returns(Promise.resolve({
body: {
getReader() {
// What's here?
},
},
}));
Is there something I can simply write in getReader or I should fully mock it like I do with fetch?

Mocked it manually:
fetchStub = stub(window, "fetch");
fetchStub.returns(Promise.resolve({
body: {
getReader() {
let i = 0;
return {
read() {
return Promise.resolve(
i < pieces.length
? { value: pieces[i++], done: false }
: { value: undefined, done: true }
);
},
};
},
},
}));

Related

Calling recursive function in loop with async/await and Promise.all

I have a use case where I'm trying to loop through an array of objects, where I need to make some GraphQL requests that may have some pagination for a given object in the array. I'm trying to speed up performance by pushing the recursive function to an array of promises, and then use Promse.all to resolve all of those.
I'm running into an issue though where I'm getting an undefined response from Promise.all - The end goal is to have the following response for each unique object in the array:
[{
account: test1,
id: 1,
high: 2039,
critical: 4059
},
{
account: test2,
id: 2,
high: 395,
critical: 203
}]
...where I'm only returning anAccount object after recursion is done paginating/making all requests for a given account object.
Here is the sample code:
const fetch = require('isomorphic-fetch');
const API_KEY = '<key>';
async function main() {
let promises = [];
let accounts = [{'name': 'test1', 'id': 1}, {'name': 'test2' , 'id': 2}];
for (const a of accounts) {
let cursor = null;
let anAccountsResults = [];
promises.push(getCounts(a, anAccountsResults, cursor));
}
let allResults = await Promise.all(promises);
console.log(allResults);
}
async function getCounts(acct, results, c) {
var q = ``;
if (c == null) {
q = `{
actor {
account(id: ${acct.id}) {
aiIssues {
issues(filter: {states: ACTIVATED}) {
issues {
issueId
priority
}
nextCursor
}
}
}
}
}`
} else {
q = `{
actor {
account(id: ${acct.id}) {
aiIssues {
issues(filter: {states: ACTIVATED}, cursor: "${c}") {
issues {
issueId
priority
}
nextCursor
}
}
}
}
}`
}
const resp = await fetch('https://my.api.com/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'API-Key': API_KEY
},
body: JSON.stringify({
query: q,
variables: ''}),
});
let json_resp = await resp.json();
let aSingleResult = json_resp.data.actor.account.aiIssues.issues.issues;
let nextCursor = json_resp.data.actor.account.aiIssues.issues.nextCursor;
console.log(nextCursor);
if (nextCursor == null) {
results = results.concat(aSingleResult);
} else {
results = results.concat(aSingleResult);
await getCounts(acct, results, nextCursor);
}
let criticalCount = results.filter(i => i.priority == 'CRITICAL').length;
let highCount = results.filter(i => i.priority == 'HIGH').length;
let anAccount = {
account: acct.name,
id: acct.id,
high: highCount,
critical: criticalCount
};
return anAccount;
}
main();
logging anAccount in function getCounts has the correct detail, but when returning it, logging the output of Promise.all(promises) yields undefined. Is there a better way to handle this in a way where I can still asynchronously run multiple recursive functions in parallel within the loop with Promise.all?
Your main problem appears to be that results = results.concat(aSingleResult); does not mutate the array you passed, but only reassigns the local variable results inside the function, so the anAccount only will use the aSingleResult from the current call.
Instead of collecting things into a results array that you pass an a parameter, better have every call return a new array. Then in the recursive await getCounts(acct, results, nextCursor) call, do not ignore the return value.
async function main() {
let promises = [];
const accounts = [{'name': 'test1', 'id': 1}, {'name': 'test2' , 'id': 2}];
const promises = accounts.map(async acct => {
const results = await getIssues(acct);
const criticalCount = results.filter(i => i.priority == 'CRITICAL').length;
const highCount = results.filter(i => i.priority == 'HIGH').length;
return {
account: acct.name,
id: acct.id,
high: highCount,
critical: criticalCount
};
});
const allResults = await Promise.all(promises);
console.log(allResults);
}
const query = `query ($accountId: ID!, $cursor: IssuesCursor) {
actor {
account(id: $accountId) {
aiIssues {
issues(filter: {states: ACTIVATED}, cursor: $cursor) {
issues {
issueId
priority
}
nextCursor
}
}
}
}
}`;
async function getIssues(acct, cursor) {
const resp = await fetch('https://my.api.com/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'API-Key': API_KEY
},
body: JSON.stringify({
query: q,
variables: {
accountId: acct.id,
cursor,
}
}),
});
if (!resp.ok) throw new Error(resp.statusText);
const { data, error } = await resp.json();
if (error) throw new Error('GraphQL error', {cause: error});
const { nextCursor, issues } = data.actor.account.aiIssues.issues;
if (nextCursor == null) {
return issues;
} else {
return issues.concat(await getIssues(acct, nextCursor));
}
}

pass an argument from axios to a method vue

I'm new to vue and I'm trying to make this work. I get some data from a XML, everything works, but I want to change which value I get from XML using a computed which gets a value from Store.
My computed is:
currentStep: {
set (val) {
this.$store.commit('setCurrentStep', val)
},
get () {
return this.$store.state.currentStep
}
}
With axios and xml2js I get all data with this Method:
getData() {
axios.get("https://something.xml").then((response) => {
this.parseXML(response.data).then((data) => {
this.flightInformations = data
})
})
},
parseXML(data) {
return new Promise((resolve) => {
let parser = new xml2js.Parser({
trim: true,
explicitArray: true,
});
parser.parseString(data, function (err, result) {
let obj = null
obj = result.flugplan.abflug[0].flug;
let flight_dates = {};
for (let item of obj) {
let flight_date = item.datum.join().toString();
if (!flight_dates[flight_date]) {
flight_dates[flight_date] = [];
}
flight_dates[flight_date].push({
flightNr: item.flugnr.join().toString(),
flightPlace: item.ort.join().toString(),
flightPlan: item.plan.join().toString(),
flightExpected: item.erwartet.join().toString(),
flightDate: item.datum.join().toString(),
})
}
resolve(flight_dates);
})
})
}
I need to change my OBJ using my computed like:
let obj = null
if (this.currentStep === 'departures') {
obj = result.flugplan.abflug[0].flug;
} else {
obj = result.flugplan.ankunft[0].flug;
}
But it does not work. Can you guys please help ?
Thank you very much.
Computed can only return some value instead of modifying anything.
Try this one:
computed: {
someData() {
return this.currentStep === 'departures' ? result.flugplan.abflug[0].flug : result.flugplan.ankunft[0].flug;
}
}
After that use a someData value:
const obj = this.someData
I get it finally and now works! here is the code, if someone have the same issue
getData() {
axios.get("something.xml").then((response) => {
this.parseXML(response.data).then((data) => {
this.flightInformations = data
})
.catch(err => {
console.log(`${err} data is not avaiable`)
})
})
},
parseXML(data) {
return new Promise((resolve) => {
let parser = new xml2js.Parser({
trim: true,
explicitArray: true,
});
parser.parseString(data, (err, result) => {
let obj = null
if (this.$store.state.currentStep === 'abflug') {
obj = result.flugplan.abflug[0].flug
} else {
obj = result.flugplan.ankunft[0].flug
}
let flight_dates = {};
for (let item of obj) {
let flight_date = item.datum.join().toString();
if (!flight_dates[flight_date]) {
flight_dates[flight_date] = [];
}
flight_dates[flight_date].push({
flightNr: item.flugnr.join().toString(),
flightPlace: item.ort.join().toString(),
flightPlan: item.plan.join().toString(),
flightExpected: item.erwartet.join().toString(),
flightDate: item.datum.join().toString()
})
}
resolve(flight_dates)
})
})
}
Now using Store, when I change my CurrentStep, it also changes which part of XML it reads.

JS - StreamSaver downlad does not start

I download data from API in chunks decrypt it and than pass to ReadableStream.
But after last chunk, the file is not downloaded.
I work with axios and StreamSaver.js
Code:
Above in the code I declare:
this.filestream = streamSaver.createWriteStream('sample.jpg');
this.writer = await this.filestream.getWriter();
let readableStream;
readableStream = new ReadableStream({
start(ctrl) {
const nextChunk = async () => {
let fileDataResponse = await that.$api.post(
'endpoint', {
file_id: UUID,
chunk_index: index
}, {
headers: {
...
}
}
);
done =
fileDataResponse.data.length <=
fileDataResponse.data.current_index;
if (fileDataResponse.data.data) {
let data = await that.decryptData(fileDataResponse.data.data);
ctrl.enqueue(data);
}
if (!done) {
index += 1;
nextChunk();
} else {
ctrl.close();
}
};
nextChunk();
}
});
const reader = readableStream.getReader();
const close = () => {
that.writer.close();
};
const pump = () =>
reader.read().then((res) => {
if (!res.done) {
that.writer.write(res.value).then(pump);
} else {
close();
}
});
pump();
Where could be my error here?
Thank you a lot!
Issue was the res.value is not an Int8Array

Asynchronous indexedDB in a class object

I have written a tiny indexedDB library but dosen't seem to work as expected. So my question is how do i fix or properly reconstruct the code bellow to work as expected with a promise. I'm out of idea.
Although the add and delete method works in terms of successfully adding and deleting item from the database but the delete method won't signal it .then function
class IDB {
constructor(name, version) {
this.name = name;
this.version = version;
this.db = null;
}
open(callback) {
const req = window.indexedDB.open(this.name, this.version);
req.onupgradeneeded = (e) => {
let db = e.target.result;
callback(db);
}
req.onsuccess = (e) => {
this.db = e.target.result;
this.db.onversionchange = () => {
this.db.close();
console.log('Tell user to reload the page');
}
}
req.onerror = (e) => {
let db = e.target.errorCode;
return;
}
}
result(req) {
return new Promise((resolve, reject) => {
if (req) {
req.onsuccess = (e) => {
let res = e.target.result;
if (res) {
resolve(res);
}
};
req.onerror = (e) => {
let err = e.target.errorCode;
if (err) {
reject(err);
}
};
}
})
}
add(store, Items) {
let req;
let tx = this.db.transaction([store], 'readwrite').objectStore(store);
Items.forEach(item => {
req = tx.add(item);
});
return this.result(req);
}
get(store, key) {
let req = this.db.transaction([store], 'readonly')
.objectStore(store).get(key);
return this.result(req);
}
cursor(store) {
let req = this.db.transaction(store).objectStore(store)
.openCursor();
return this.result(req);
}
delete(store, key) {
let req = this.db.transaction([store], 'readwrite')
.objectStore(store).delete(key);
return this.result(req);
}
}
Usage
const employeeData = [
{ id: "00-01", name: "gopal", age: 35, email: "gopal#tutorialspoint.com" },
{ id: "00-02", name: "prasad", age: 32, email: "prasad#tutorialspoint.com" }
];
const idb = new IDB('mydb', 1);
idb.open((db) => {
if (!db.objectStoreNames.contains('user')) {
db.createObjectStore('user', {keyPath: 'id'});
}
});
idb.add('user', employeeData).then(() => alert('Items added')).catch(() => alert('failed'));
But I would prefer the usage with the async/await code
await idb.add('..', ..) // blah blah

How do you pass variables to other JS modules with Fetch API?

A JavaScript app uses Web Audio API to create sounds from JSON data. I am fetching weather data, going through the JSON data and setting their properties to variables then using those variables to manipulate my application and create sounds. Each function in it's own JavaScript module script file. The main.js not shown here is the entry point to app.
A sample JSON that will get replaced with real weather data.
dummy-data.json
{
"weather": {
"temp": 4,
"rain": 1,
"wind": 1.2
}
}
The fetch API logic.
fetchWeather.js
import { manageData} from './manageScript.js';
const DUMMY = '../dummy-data.json';
const fetchWeather = () => {
fetch(DUMMY)
.then((res) => {
return res.json();
})
.then((data) => {
manageData(data); // attaches JSON weather properties to variables
})
.catch((error) => {
console.log(error);
});
};
export { fetchWeather };
Attaches the JSON data to variables.
manageScript.js
function manageData(data) {
let rain = data.weather.rain;
//let wind = data.weather.wind;
let rainProbability;
if (rain == 1) {
rainProbability = 1;
}
else {
rainProbability = 0;
}
return rainProbability; // not sure how to return the data....?
};
export { manageData };
I want the variables from manageData function above to work here
makeSynth.js
import { manageData } from './manageScript.js';
const createSynth = () => {
//Web Audio API stuff goes here to create sounds from the variables.
//How do I get the variables to work here. Code below does not work!
let soundOfRain = manageData().rainProbability;
console.log(soundOfRain);
};
You can achieve this by refactoring your promises into a async/await pattern then returning the result (a different method of dealing with promises). Also - your createSynth function should be calling fetchWeather, not manageScript
dummy-data.json
{
"weather": {
"temp": 4,
"rain": 1,
"wind": 1.2
}
}
manageScript.js
function manageData(data) {
let rain = data.weather.rain;
//let wind = data.weather.wind;
let rainProbability;
if (rain == 1) {
rainProbability = 1;
} else {
rainProbability = 0;
}
return rainProbability;
}
export { manageData };
fetchWeather.js
import { manageData } from "./manageScript.js";
const DUMMY = "../dummy-data.json";
// Use async/await to be able to return a variable out from the promise
const fetchWeather = async () => {
const raw = await fetch(DUMMY);
const json_data = await raw.json();
const rain = manageData(json_data);
// Now you should be able to return the variable back out of the function
return rain;
};
export { fetchWeather };
makeSynth.js
import { fetchWeather } from "./fetchWeather.js";
const createSynth = async () => {
//Web Audio API stuff goes here to create sounds from the variables.
//Need to call fetchWeather (which in turn will call manageData)
let soundOfRain = await fetchWeather();
console.log(soundOfRain);
};
createSynth();
// dummy-data.json
{
"weather": {
"temp": 4,
"rain": 1,
"wind": 1.2
}
}
// fetchWeather.js
import { getRainProbability } from './get-rain-probability.js'
import { createSynth } from './create-synth.js'
const DUMMY = '../dummy-data.json'
const fetchWeather = () => {
return fetch(DUMMY)
.then((res) => res.json())
.then((data) => {
createSynth({ rainProbability: getRainProbability(data) })
})
.catch((error) => {
console.log(error)
});
};
export { fetchWeather }
// get-rain-probability.js
function getRainProbability(data) {
let rain = data.weather.rain
let rainProbability;
if (rain == 1) {
rainProbability = 1;
}
else {
rainProbability = 0;
}
return rainProbability; // not sure how to return the data....?
};
// create-synth.js
const createSynth = ({ rainProbability }) => {
const soundOfRain = //WebAPI stuff for audio using `rainProbability`
console.log(soundOfRain);
};
export { createSynth }
You can add data as a property of manageData that would return this, and access it with manageData().data; :
fetchWeather.js
const fetchWeather = () => {
fetch(DUMMY)
.then(res => {
return res.json();
})
.then(data => {
manageData.data = data; // attaches JSON weather properties to variables
})
.catch(error => {
console.log(error);
});
};
manageScript.js
function manageData() {
// ...
return this;
}
makeSynth.js
let soundOfRain = manageData().data.rainProbability;

Categories

Resources