I want to make this code work
$('start') // push initial value to an array
.addItem(2) // add another value
.printAll() // print everything in array
.delay(2000) // wait x seconds
.addItem(5)
.printAll()
.start(); // execute all the commands before this line
I created a class with data array to hold the items. and steps array to hold the operations. Then i used chainable promises to execute them. is that the best way to do what i'm trying to achieve? Do I really need to store the operations in an array?
class Sync {
constructor() {}
init(startValue) {
this.data = [startValue];
this.steps = [];
return this;
}
addItem(value) {
const append = (v)=>this.data.push(v);
this._addStep(append, value);
return this;
}
printAll() {
this._addStep(v=>console.log(v.join()), this.data);
return this;
}
delay(value) {
this._addStep(window.setTimeout, value);
return this;
}
_addStep(fun, ...args) {
this.steps.push({
fun,
args
});
}
start() {
let start = Promise.resolve();
this.steps.forEach(({fun, args})=>{
start = start.then(()=>{
return new Promise(resolve=>{
if (fun === window.setTimeout) {
setTimeout(resolve, ...args);
} else {
fun.apply(this, args);
resolve();
}
}
);
}
);
}
);
return this;
}
}
const lib = new Sync();
const $ = lib.init.bind(lib);
$('start')
.addItem(2)
.printAll()
.delay(2000)
.addItem(5)
.printAll()
.start();
Although your question belongs to https://codereview.stackexchange.com/ according to me, I tried to think of another implementation without Promises. It works with closures and callbacks only:
class Sync {
constructor() {}
init(startValue) {
this.data = [startValue];
this.steps = [];
return this;
}
addItem(value) {
const append = v => this.data.push(v);
this._addStep(append, value);
return this;
}
printAll() {
this._addStep(v => console.log(v.join()), this.data);
return this;
}
delay(value) {
this._addStep(window.setTimeout, value);
return this;
}
_addStep(fun, ...args) {
this.steps.push({
fun,
args
});
}
start() {
let finalFunction;
this.steps.reverse().forEach(({ fun, args }) => {
if (fun === window.setTimeout) {
finalFunction = finalFunction ? encloseFunctionWithArgs(null, null, finalFunction, next => setTimeout(next, ...args)) : null;
} else {
finalFunction = encloseFunctionWithArgs(fun, args, finalFunction);
}
});
finalFunction();
return this;
}
}
function encloseFunctionWithArgs(fun, args, next, trigger) {
return function () {
if (fun)
fun(args);
if (next)
trigger ? trigger(next) : next();
}
}
const lib = new Sync();
const $ = lib.init.bind(lib);
$('start')
.addItem(2)
.printAll()
.delay(2000)
.addItem(5)
.printAll()
.start();
EDIT
I finally managed to achieve what you want:
const base = {
init(startValue) {
this.promise = new Promise(resolve => this.start = resolve).then(() => [startValue]);
return this;
},
addItem(value) {
this.promise = this.promise.then(v => [...v, value]);
return this;
},
printAll() {
this.promise.then(v => v.join()).then(console.log);
return this;
},
delay(value) {
this.promise = this.promise.then(v => new Promise(resolve => setTimeout(() => resolve(v), value)));
return this;
}
}
const factory = base => arg => Object.create(base).init(arg);
const $ = factory(base);
$('start')
.addItem(2)
.printAll()
.delay(2000)
.addItem(5)
.printAll()
.start();
Related
I want to implement a function to dynamically load js scripts, if the loading fails, retry it, the retry limit is three times, and return the loading result.
async function getScript(url: string, retryTime = 0, promise?: any) {
const element = window.document.createElement('script');
element.src = url;
element.type = 'text/javascript';
element.async = true;
const p = promise || {
resolve: (v: boolean) => { },
reject: (msg: string) => { },
};
element.onload = () => {
p.resolve(true);
};
element.onerror = () => {
const msg = `Dynamic Script Error: ${url}`;
if (retryTime < 3) {
console.log('retry load');
document.head.removeChild(element);
return getScript(url, retryTime + 1, p);
}
p.reject(msg);
};
document.head.appendChild(element);
return new Promise((rl, rj) => {
p.resolve = rl;
p.reject = rj;
});
}
But when I call this function, if load error in first time, and success in second time. document.head.appendChild(element)will throw an error, So, i can't catch the error in outside.
What can I do.
Seems overly complicated. Here's a cleaner, more decoupled solution:
async function getScript(url) {
return new Promise((resolve, reject) => {
const element = document.createElement('script');
element.src = url;
element.type = 'text/javascript';
element.async = true;
element.onload = () => resolve();
element.onerror = () => reject();
document.head.appendChild(element);
});
}
async function retrying(func, attempts) {
for (let attempt = 1; attempt <= attempts; attempt++) {
try {
return await func();
} catch (ex) {
console.warn(`Failed in attempt ${attempt}`);
}
}
throw new Error(`Failed after ${attempts} attempts`);
}
Usage:
retrying(() => getScript(url), 3);
If I have three types of functions, which return, respectively,
A synchronous value:
function a(){
return 'a';
}
A callback:
function b(callback){
setTimeout(() => {
callback('b');
}, 10);
}
a Promise:
function c() {
return Promise.resolve('c')
}
How can I combine the result of all 3 in a single promise? Can I create a function which will return ['a', 'b', 'c']? I do not want to change these three functions.
How can we combine a synchronous function, a callback function and a Promise all together?
Promisify the callback, and send all three through Promise.all.
function a() {
return "a";
}
function b(callback) {
setTimeout(() => {
callback('b');
}, 10);
}
const bProm = () => new Promise(b);
function c() {
return Promise.resolve('c')
}
Promise.all([
a(),
bProm(),
c(),
])
.then((results) => {
console.log(results);
});
Given the functions:
function a(){
return 'a';
}
function b(callback){
setTimeout(() => {
callback('b');
}, 10);
}
function c() {
return Promise.resolve('c');
}
You can wrap them all in a promise using Promise.all(). The solution will depend on your requirements and limitations.
The function b(callback) must be wrapped, or be changed to accomplish this, because it has a setTimeout within it.
Wrapped version
function bWrapped (callback) {
return new Promise(
resolve =>
b(()=> {
resolve(callback('b'));
}
)
}
A function to return the expected result would be like:
const abc = () => {
const functionAPromise = Promise.resolve(a())
const callback = function(b){return b}
const functionBPromise = bWrapped(callback)
return Promise.all([functionAPromise,functionBPromise,c()])
}
Changed version
function b(callback){
return new Promise(resolve => setTimeout(()=> resolve(callback('b')), 10))
}
A function to return the expected result would be like:
const abc = () => {
const functionAPromise = Promise.resolve(a())
const callback = function(b){return b}
return Promise.all([functionAPromise,b(callback),c()])
}
Just check the result of each function and handle them.
function a() {
return "a";
}
function b(callback) {
setTimeout(() => callback("b"), 10);
}
function c() {
return Promise.resolve("c");
}
const functionList = [a, b, c];
function runFunctionList(list, callback) {
const resultList = [];
const called = {};
function next() {
if (resultList.length === list.length) {
for (let i = 0; i < resultList.length; i++) {
if (resultList[i] === undefined) {
return;
}
}
callback(resultList);
resultList.length = 0;
}
}
list.forEach((li, index) => {
const ret = li(value => {
resultList[index] = value;
next();
});
if (ret instanceof Promise) {
// it's promise
ret.then(value => {
resultList[index] = value;
next();
});
} else if (ret === undefined) {
// it's callback function
} else {
// it's synchronous function
resultList[index] = ret;
}
});
next();
}
runFunctionList(functionList, allValue => {
console.log("allValue here:", allValue);
});
Sequential execution:
function a() {
return 'a';
}
function b(callback) {
setTimeout(() => {
callback('b');
}, 10);
}
function c() {
return Promise.resolve('c');
}
function abc() {
return new Promise(resolve => {
const valueA = a();
b(valueB => {
c().then(valueC => {
resolve([valueA, valueB, valueC]);
});
});
});
}
abc().then(result => {
console.log(result);
});
I'm trying to Create a Promise that resolves when variable is not undefined.
Code example
https://codesandbox.io/s/quizzical-feynman-ktvox?file=/src/index.js
let fetchedPromise = new Promise((resolve, reject) => {
const check = ()=>{
setTimeout(() =>{
console.log("checking")
if (dataFetched) resolve(dataFetched);
else check()
}, 100);
}
check()
});
const waitForDataFromAnotherComponent = async () => {
let result = await fetchedPromise;
console.log("Result: ", result);
};
const assignData = () => {
setTimeout(() => {
dataFetched = 1000;
console.log(dataFetched);
}, 5000)
};
waitForDataFromAnotherComponent();
assignData();
This works but I find it inefficient as it's callstack prone and seems wrong.
Other non-working solutions I've tried:
//-------------SOLUTION 1
let fetchedPromise = new Promise((resolve, reject) => {
const check = ()=>{
if (dataFetched) resolve(dataFetched);
else check()
}
check()
});
//--------------------SOLUTION 2
let fetchedPromise = new Promise((resolve, reject) => {
if (dataFetched) resolve(dataFetched);
});
Scenario
I need a function like solution 3 that doesn't rely on setTimeout
Solved by using Javascript Proxy
Basically I assign a Proxy Object to dataFetched that listens to changes.
I re-create the function of listening due to the fact that it must include resolve()
let dataFetched
let x = {
aListener: function (val) {},
set a(val) {
dataFetched = val;
this.aListener(val);
},
get a() {
return dataFetched;
},
registerListener: function (listener) {
this.aListener = listener;
}
};
let fetchedPromise = new Promise((resolve, reject) => {
x.registerListener(function (val) {
console.log("yeyyyyyyyyyyyyyyy");
if (dataFetched) resolve(dataFetched);
});
});
const waitForDataFromAnotherComponent = async () => {
let result = await fetchedPromise;
console.log("Result: ", result);
};
const assignData = async () => {
await new Promise((resolve, reject) =>
setTimeout(() => {
x.a = 1000;
console.log(dataFetched);
resolve(dataFetched);
}, 1000)
);
};
waitForDataFromAnotherComponent();
assignData();
EDIT
Actually it's possible to externalize the resolve() function of the promise but with some downsides as stated here
example
let dataFetched
var promiseResolve, promiseReject;
let x = {
aListener: function (val) {
if (dataFetched) promiseResolve(dataFetched);
},
set a(val) {
dataFetched = val;
this.aListener(val);
},
get a() {
return dataFetched;
},
registerListener: function (listener) {
this.aListener = listener;
}
};
let fetchedPromise = new Promise((resolve, reject) => {
promiseResolve = resolve;
promiseReject = reject;
});
I have this code:
var c = function(address, abiJson){
var _ = this;
this.data = {
wallet: false,
account:{
address: false
},
contract:{
address: address
}
};
this.abi = $.getJSON(abiJson, function(abi){
_.data.abi = abi;
if(typeof web3 !== 'undefined'){
window.web3 = new Web3(web3.currentProvider);
window.cont = web3.eth.contract(abi).at(address);
}
});
this.getData = function(cb){
if(typeof _.data.abi !== 'undefined'){
_.updateData.done(() => {
cb(_.data);
});
}
else{
_.abi.then(() => {_.updateData
})
.done(() => {
cb(_.data);
});
}
}
this.updateData = Promise.all([
_.get('x'),
_.get('y')
])
.then(values => {
_.data.x.y= values[0];
_.data.x.z= values[1];
})
.then(() => {
Promise.all([
_.get('v', 1),
_.get('v', 2),
])
.then(values => {
_.data.y = values;
});
});
this.get = function(method, args){
return new Promise(function(resolve, reject) {
window.cont[method](args, function(error, result){
if(!error) resolve(result);
});
});
}
}
When I get the function _.get('x').then((x) => console.log (x)) outside the updateData function, I get all the data. But when I call the getData function I get errors for all the get functions _.get is not a function.
I just don't see where is my mistake. I'm new in js using Promise.
Here's a cut-down version of the question:
var C = function(address, abiJson){
var _ = this;
this.updateData = Promise.all([
_.get('x'),
_.get('y')
]);
this.get = function( arg){ return Promise.resolve( arg)};
}
var c = new C();
c.updateData.then( values => console.log(values));
Calling get inside the array initializer argument of the Promise.all call errors because get has yet to be added as a method of _ (which is set to this). Adding the get method before creating the array solves the immediate problem:
var C = function(address, abiJson){
var _ = this;
// add get method first:
this.get = function( arg){ return Promise.resolve( arg)};
this.updateData = Promise.all([
_.get('x'),
_.get('y')
]);
}
var c = new C();
c.updateData.then( values => console.log(values));
_ variable cannot be used that way. You can use it when you call it as a function, but cannot be used when you are creating an object.
You can bind the scope using bind for the functions. For arrow functions, the scope will be retained. Try the following code. I just replaced _ with this for all the functions and binded this to the callback of getJSON. If you use arrow function there also, bind will not be necessary.
var c = function(address, abiJson){
this.data = {
wallet: false,
account:{
address: false
},
contract:{
address: address
}
};
this.abi = $.getJSON(abiJson, function(abi){
this.data.abi = abi;
if(typeof web3 !== 'undefined'){
window.web3 = new Web3(web3.currentProvider);
window.cont = web3.eth.contract(abi).at(address);
}
}.bind(this));
this.getData = function(cb){
if(typeof this.data.abi !== 'undefined'){
this.updateData.done(() => {
cb(this.data);
});
}
else{
this.abi.then(() => {this.updateData
})
.done(() => {
cb(this.data);
});
}
}
this.get = function(method, args){
return new Promise(function(resolve, reject) {
window.cont[method](args, function(error, result){
if(!error) resolve(result);
});
});
}
this.updateData = Promise.all([
this.get('x'),
this.get('y')
])
.then(values => {
this.data.x.y= values[0];
this.data.x.z= values[1];
})
.then(() => {
Promise.all([
this.get('v', 1),
this.get('v', 2),
])
.then(values => {
this.data.y = values;
});
});
}
I am trying to build a way to create a generator which can yield DOM events. More generally, I want to create a way to convert an event system to an async system yielding events.
My initial code example works, but I can see an issue with lifting the resolve function from the Promise so that I can call that function once the event comes in.
class EventPropagation {
constructor(id) {
const button = document.getElementById(id);
let _resolve;
button.addEventListener("click", event => {
if (_resolve) {
_resolve(event);
}
});
let _listen = () => {
return new Promise(resolve => {
_resolve = resolve;
});
}
this.subscribe = async function*() {
const result = await _listen();
yield result;
yield * this.subscribe();
}
}
}
async function example() {
const eventPropagation = new EventPropagation("btn");
for await (const event of eventPropagation.subscribe()) {
console.log(event);
}
}
// call the example function
example();
My question is: Is there a better way of building something like this? There are a lot of things to think about, like multiple events coming in at the same time or cleaning up the listener and the subscriptions. My goal is not to end up with a reactive library but I do want to create small transparent functions which yield events asynchronously.
fiddle
Edited 14 dec 2017 (Edited in response to Bergi's comment)
Async Generators
Babel and a few plugins later; async generators aren't a problem:
const throttle = ms => new Promise(resolve => setTimeout(resolve, ms));
const getData = async() => {
const randomValue = Math.floor(Math.random() * 5000 + 1);
await throttle(randomValue);
return `The random value was: ${randomValue}`;
}
async function* asyncRandomMessage() {
const message = await getData();
yield message;
// recursive call
yield *asyncRandomMessage();
}
async function example() {
for await (const message of asyncRandomMessage()) {
console.log(message);
}
}
// call it at your own risk, it does not stop
// example();
What I want to know is how I transform a series of individual callback calls into an async stream. I can't imagine this problem isn't tackled. When I look at the library Bergi showed in the comments I see the same implementation as I did, namely: "Store the resolve and reject functions somewhere the event handler can call them." I can't imagine that would be a correct way of solving this problem.
You need a event bucket, here is an example:
function evtBucket() {
const stack = [],
iterate = bucket();
var next;
async function * bucket() {
while (true) {
yield new Promise((res) => {
if (stack.length > 0) {
return res(stack.shift());
}
next = res;
});
}
}
iterate.push = (itm) => {
if (next) {
next(itm);
next = false;
return;
}
stack.push(itm);
}
return iterate;
}
;(async function() {
let evts = evtBucket();
setInterval(()=>{
evts.push(Date.now());
evts.push(Date.now() + '++');
}, 1000);
for await (let evt of evts) {
console.log(evt);
}
})();
My best solution thus far has been to have an internal EventTarget that dispatches events when new events are added onto a queue array. This is what I've been working on for a JS modules library (including used modules here). I don't like it... But it works.
Note: This also handles the new AbortSignal option for event listeners in multiple places.
export function isAborted(signal) {
if (signal instanceof AbortController) {
return signal.signal.aborted;
} else if (signal instanceof AbortSignal) {
return signal.aborted;
} else {
return false;
}
}
export async function when(target, event, { signal } = {}) {
await new Promise(resolve => {
target.addEventListener(event, resolve, { once: true, signal });
});
}
export async function *yieldEvents(what, event, { capture, passive, signal } = {}) {
const queue = [];
const target = new EventTarget();
what.addEventListener(event, event => {
queue.push(event);
target.dispatchEvent(new Event('enqueued'));
}, { capture, passive, signal });
while (! isAborted(signal)) {
if (queue.length === 0) {
await when(target, 'enqueued', { signal }).catch(e => {});
}
/**
* May have aborted between beginning of loop and now
*/
if (isAborted(signal)) {
break;
} else {
yield queue.shift();
}
}
}
The example provided by NSD, but now in Typescript
class AsyncQueue<T> {
private queue: T[] = [];
private maxQueueLength = Infinity;
private nextResolve = (value: T) => {};
private hasNext = false;
constructor(maxQueueLength?: number) {
if (maxQueueLength) {
this.maxQueueLength = maxQueueLength;
}
}
async *[Symbol.asyncIterator]() {
while (true) {
yield new Promise((resolve) => {
if (this.queue.length > 0) {
return resolve(this.queue.shift());
}
this.nextResolve = resolve;
this.hasNext = true;
});
}
}
push(item: T) {
if (this.hasNext) {
this.nextResolve(item);
this.hasNext = false;
return;
}
if (this.queue.length > this.maxQueueLength) {
this.queue.shift();
}
this.queue.push(item);
}
}
(async function () {
const queueu = new AsyncQueue<string>();
setInterval(() => {
queueu.push(Date.now().toString());
queueu.push(Date.now().toString() + "++");
}, 1000);
for await (const evt of queueu) {
console.log(evt);
}
})();