Get Promise resolve from separate callback - javascript

I am sending data to a Bluetooth device, and the responses are handled by a listener that's set up during the connection process:
device.connect().then(device => {
device.registerResponseListener((data) => {
// handle response
}
}
I have a separate function that sends data to the device:
const sendData = (device, data) => {
device.write(data);
}
My question is, how can I Promisify this code? I'd like to be able to do
const sendData = (device, data) => {
return new Promise((resolve, reject) => {
device.write(data);
// resolve...?
});
}
But how do I get the resolve into the Bluetooth response listener?

I don't know what API you're using but you can try BluetoothRemoteGATTCharacteristic API. It has writeValueWithResponse method which return Promise.
https://developer.mozilla.org/en-US/docs/Web/API/BluetoothRemoteGATTCharacteristic

If I understood you correctly then you can do it like this
const sendData = async (device, data) => {
const response = await device.write(data);
await Promise.all(device.responseListeners.map(listener => listener(response)))
}

The best possible solution in this case, while still not ideal, was to store the resolve function in variable at a higher scope:
var sendDataResolve;
device.connect().then(device => {
device.registerResponseListener((data) => {
sendDataResolve(data);
}
}
const sendData = (device, data) => {
return new Promise((resolve, reject) => {
sendDataResolve = resolve;
device.write(data);
});
}
...
sendData(device, "data")
.then(result => {
console.log("Got result",result);
});
The caveat is that Promise resolutions are NOT guaranteed to be tied correctly to the original request. This ONLY works with one request at a time.

Related

JavaScript Dynamic Promises

I am trying to understand how promises work in JS by playing with swapi.dev. I would like to create a dynamic chain of promises (not using async/await) but it does not provide me with any result. In particular, the idea behind is to get all names of the given person (for instance Luke Skywalker) and dump them into the console.
Could anyone help me? What am I missing?
Thanks in advance.
"use strict";
const request = require("request-promise");
const BASE_URL = "http://swapi.dev/api";
var currentPromise = Promise.resolve();
callApiPromise(`${BASE_URL}/people/1`).then((data) => {
console.log("Getting vehicles' URLs");
const vehicles_URL = data["vehicles"];
console.log("Starting looping through URLs");
for (let i = 0; i < vehicles_URL.length; i++) {
console.log(`i=${i}, vehicle_URL=${vehicles_URL[i]}`);
currentPromise = currentPromise.then(function () {
console.log(".. getting vehicle name");
return getVehicleName[vehicles_URL[i]];
});
}
});
function getVehicleName(url) {
callApiPromise(url).then((vehicle_data) => {
var arrVehicleData = new Array();
arrVehicleData.push(vehicle_data);
console.log(arrVehicleData.map((vehicle) => vehicle.name));
});
}
function callApiPromise(url) {
return new Promise((resolve, reject) => {
callApi(url, (err, data) => {
if (err) {
reject(err);
return;
}
resolve(data);
});
});
}
function callApi(url, callback) {
request
.get(url)
.then((response) => {
const json = JSON.parse(response);
callback(null, json);
})
.catch((err) => {
callback(err, null);
});
}
Some issues:
A missing return statement in getVehicleName
A syntax issue in getVehicleName[vehicles_URL[i]] (should be parentheses)
As the promises for getting the vehicle names are independent, you would not chain them, but use Promise.all
arrVehicleData will always only have one element. There is no reason for an array there where it is used.
You are also taking the wrong approach in using request.get. The bottom function turns that API from a Promise-API to a callback API, only to do the reverse (from callback to promise) in the function just above it. You should just skip the callback layer and stick to promises:
"use strict";
const request = require("request-promise");
const BASE_URL = "http://swapi.dev/api";
getJson(`${BASE_URL}/people/1`).then(data => {
return Promise.all(data.vehicles.map(getVehicleName));
}).then(vehicleNames => {
console.log(vehicleNames);
// Continue here...
});
function getVehicleName(url) {
return getJson(url).then(vehicle => vehicle.name);
}
function getJson(url, callback) {
return request.get(url).then(JSON.parse);
}
Finally, you should not use request-promise anymore since the request module, on which request-promise depends, has been deprecated
The getVehicleName doesn't return a promise. Instead it invokes a promise that by the time it will be resolved, the for loop invoking it will already be removed from the call stack.
This is a sample of promise chaining:
const promise = new Promise(resolve => resolve(1))
const promise1 = Promise.resolve(2)
const methodReturnPromise = () => new Promise(resolve => resolve(3))
promise.then(firstPromiseData => {
// do something with firstPromiseData
console.log(firstPromiseData)
return promise1
}).then(secondPromiseData => {
// do something with secondPromiseData
console.log(secondPromiseData)
return methodReturnPromise()
}).then(thirdPromiseData => {
// do something with thirdPromiseData
console.log(thirdPromiseData)
})

Cypress - how to properly wait for result of an imported JS function

I am new to Cypress (and naive to JS). I would like to write a JS library as a wrapper to 3rd party APIs.
I write the API wrapper as an individual file (instead of using Cypress Custom functions) because I believe I can share the library with teams NOT using Cypress E2E tool.
The problem I am facing is "I cannot let my code to be executed sequentially in order"
From the result, I can see:
the data didn't return successfully
it looks like the "getTestPlanIdByName:20974" were executed last, but I expect it should be executed before "line 01b testPlanId:{}"
I need to help to know the correct way to handle the flow sequentially in Cypress/Javascript, thanks.
API Library(api-util.js)
let axios = require('axios');
const proxy = "http://10.8.8.8:8080/";
const apiPatToken = 'OmdrvbvvvvvvvvWZqa2E='
let proxyAgentHttps = require('https-proxy-agent');
let proxyAgentHttp = require('http-proxy-agent');
let agentHttps = new proxyAgentHttps(proxy);
let agentHttp = new proxyAgentHttp(proxy);
let config = {
baseURL: 'https://dev.3rdparty.com/mycompany/myaccount/_apis',
url: 'DUMMY_INJECTED_LATER',
httpsAgent: agentHttps,
httpAgent: agentHttp,
proxy:false,
headers: {
'Authorization': `Basic ${apiPatToken}`
}
}
export async function getTestPlanIdByName(testplan_name){
config.url = '/test/plans?api-version=5.0'
let found = ''
axios.request(config).then( resp => {
found = resp.data.value.find(function(item, index, array){
return item.name === testplan_name
})
})
.then(() => {
console.log("getTestPlanIdByName:"+found.id)
return found.id
})
.catch(err => console.log(err))
}
My Cypress code
import * as UTIL from 'api-util.js'
describe('CI-', () => {
let testPlanId = 'none'
it('01 Get TestPlanID', () => {
//use cy.log() get a Promise for flow control
cy.log()
.then(() => {
new Cypress.Promise((resolve, reject) => {
console.log("01a testPlanId:"+JSON.stringify(testPlanId))
testPlanId = UTIL.getTestPlanIdByName("TESTPLAN-Regression")
console.log("01b testPlanId:"+JSON.stringify(testPlanId))
})
})
.then(() => {
console.log("01c testPlanId:"+JSON.stringify(testPlanId))
})
});
it('02 Get TestSuitesList', () => {
console.log("02 testPlanId:"+testPlanId)
// UTIL.getTestSuitesIdList(testPlanId)
});
});
Thank you all. Cypress flow isn't 100% compatible with standard JS Promise (Wait for an own function (which returns a promise) before tests are executed). After relentless testings, I decided to use a Cypress Custom Command wrapper to wrap my in-house JS library. Though adding an extra layer may seem a little cumbersome. But I am satisfied with the result. Share my code here in case anyone might need it. :)
Cypress Code
before('Prepare TestPlanId', () => {
cy.getTestPlanIdByName(testPlanName)
.then((result) => {
testPlanId = result
console.log("#01_SDET_testplan:Prepare TestPlanId# "+testPlanId)
})
});
Cypress Custom Command
Cypress.Commands.add('getTestPlanIdByName', (wk_testplan_name) => {
return new Cypress.Promise((resolve, reject) => {
TESTPLAN_API.getTestPlanIdByName(wk_testplan_name)
.then(function (data) {
resolve(data);
})
});
})
In-house JS library
export async function getTestPlanIdByName(wk_testplan_name){
return new Promise((resolve, reject) => {
config.method = 'get'
config.url = '/test/plans?api-version=5.0'
let found = ''
axios.request(config).then( resp => {
found = resp.data.value.find(function(item, index, array){
return item.name === wk_testplan_name
})
})
.then(() => {
resolve(found.id)
})
.catch(err => console.log(err))
})
}

Promise data and exception handling

I am confused with the use of promise, specifically of its way of data manipulation (passing values from block to block) and exception handling (bubbling up the error). I am trying to learn a right way to use promise and to handle error, something like
Error: A caught error.
at promiseTwo()
at promiseOne()
at subprocess()
at mainprocess()
Here are my two attempts in implementing them:
Attempt 1: Clumsy, deeply nested, and errors are uncaught.
var subprocess = () => {
return new Promise((resolve, reject) => {
promiseOne().then(data1 => {
// Some code with data1, throw some error
promiseTwo().then(data2 => {
// Some code with data1n2, throw some error
promiseThree().then(data3 => {
// Data manipulation with data1, data2, and data3
return resolve(<...>)
}).catch(err3 => { throw err3 })
}.catch(err2n3 => { throw err2n3 }) // >>> ERR: Cannot get err3.
}.catch(err1n2n3 => { return reject(err1n2n3) }) // >>> ERR: Cannot get err3 or err2.
}
}
return new Promise((resolve, reject) => {
subprocess().then(data => {
// TODO
}).catch(allErr => { return reject(allErr) }
}
Attempt 2: Unable to use data from previous promise block.
var subprocess = () => {
return new Promise((resolve, reject) => {
promiseOne()
.then(data1 => {
// Some code with data1, throw some error
return promiseTwo()
})
.then(data2 => {
// Some code with data1n2, throw some error
// >>> ERR: Cannot get data1
return promiseThree()
})
.then(data3 => {
// Data manipulation with data1, data2, and data3
// >>> ERR: Cannot get data1 and data2
return resolve(<...>)
})
.catch(err1n2n3 => {
return reject(err1n2n3)
})
}
}
return new Promise((resolve, reject) => {
subprocess().then(data => {
// Some code, throw some error
}).catch(allErr => { return reject(allErr) }
}
Note: Some of the promise block (i.e. promiseOne, promiseTwo, etc.) are pre-defined so I do not have control over what data they will return. I am sure there are more errors in the attempts (e.g. if returning a function is a right way to do it).
Please help. Thanks.
for this kind of situation, you can combine promises and async-await together.
From the question, it seems we have three promises and one function that executes and handle them.
You can try something like this -
const subProcess = () => {
return new Promise((resolve, reject) => {
// Using IIFE ( You shouldn't put async keyword on promise callbac )
(async () => {
// Use of try catch to handle the errors
try {
await promiseOne()
await promiseTwo()
await promiseThree()
// Additional code if need after them
} catch(err){
// Handle error ( all three promise error will be transferred here )
}
})()
})
}
The above code waits for the promises to execute one by one and also catch error from all three promises if any.
And as #samuei mentioned, you can also use Promise.all() in this.
const subProcess = () => {
return new Promise((resolve, reject) => {
// Using IIFE ( You shouldn't put async keyword on promise callbac )
(async () => {
// Use of try catch to handle the errors
try {
const myPromises = [promiseOne, promiseTwo, promiseThree];
const res = await Promise.all(myPromises);
// Additional code if need after them
} catch(err){
// Handle error ( all three promise error will be transferred here )
}
})()
})
}
And if you don't want to use async-await then you can do something like this as well
const subProcess = () => {
return new Promise((resolve, reject) => {
const myPromises = [];
const myPromises = [promiseOne, promiseTwo, promiseThree];
Promise.all(myPromises)
.then(res => {
// Handle the response
})
.catch(err => {
// Handle the error
})
})
}
It sounds like you're looking for Promise.all, which lets you set a series of promises in motion, then deal with the results when they are all resolved.

Writing a generic HTTP request in NodeJS

I am making an app that creates multiple HTTP requests to different APIs, and would like to abstract the "sending" and "getting" portion of the data into a utility function that can be exported. It should be async. So far, I have created the following, but it won't work since request.end() needs to be declared somewhere, and I'm stuck here. Thanks!
Note: the resulting data should be able to be easily returned to some other function on order to do other work with it later on.
exports.handleHttpRequests = (url) => {
return new Promise((resolve, reject) => {
const request = http.request(url, response => {
let chunks = [];
res.on('data', chunk => {
chunks.push(chunk);
});
res.on('end', () => {
let body = Buffer.concat(chunks).toString();
resolve(body);
})
})
}).then(data => {
return data;
});
}
Hey you already done all, just call request.end() inside your new Promise callback & also handle error . And do what you want to do with resolve ,reject.
exports.handleHttpRequests = (url) => {
return new Promise((resolve, reject) => {
const request = http.request(url, res => {
let chunks = [];
res.on('data', chunk => {
chunks.push(chunk);
});
res.on('end', () => {
let body = Buffer.concat(chunks).toString();
resolve(body);
})
});
//handling error
request.on('error', (e) => {
reject('problem with request: ' + e.message);
});
//here you have to put request.end()
request.end();
})
};

Is it safe to resolve a promise multiple times?

I have an i18n service in my application which contains the following code:
var i18nService = function() {
this.ensureLocaleIsLoaded = function() {
if( !this.existingPromise ) {
this.existingPromise = $q.defer();
var deferred = this.existingPromise;
var userLanguage = $( "body" ).data( "language" );
this.userLanguage = userLanguage;
console.log( "Loading locale '" + userLanguage + "' from server..." );
$http( { method:"get", url:"/i18n/" + userLanguage, cache:true } ).success( function( translations ) {
$rootScope.i18n = translations;
deferred.resolve( $rootScope.i18n );
} );
}
if( $rootScope.i18n ) {
this.existingPromise.resolve( $rootScope.i18n );
}
return this.existingPromise.promise;
};
The idea is that the user would call ensureLocaleIsLoaded and wait for the promise to be resolved. But given that the purpose of the function is to only ensure that the locale is loaded, it would be perfectly fine for the user to invoke it several times.
I'm currently just storing a single promise and resolve it if the user calls the function again after the locale has been successfully retrieved from the server.
From what I can tell, this is working as intended, but I'm wondering if this is a proper approach.
As I understand promises at present, this should be 100% fine. The only thing to understand is that once resolved (or rejected), that is it for a defered object - it is done.
If you call then(...) on its promise again, you immediately get the (first) resolved/rejected result.
Additional calls to resolve() will not have any effect.
Below is an executable snippet that covers those use cases:
var p = new Promise((resolve, reject) => {
resolve(1);
reject(2);
resolve(3);
});
p.then(x => console.log('resolved to ' + x))
.catch(x => console.log('never called ' + x));
p.then(x => console.log('one more ' + x));
p.then(x => console.log('two more ' + x));
p.then(x => console.log('three more ' + x));
I faced the same thing a while ago, indeed a promise can be only resolved once, another tries will do nothing (no error, no warning, no then invocation).
I decided to work it around like this:
getUsers(users => showThem(users));
getUsers(callback){
callback(getCachedUsers())
api.getUsers().then(users => callback(users))
}
just pass your function as a callback and invoke it as many times you wish! Hope that makes sense.
There s no clear way to resolve promises multiple times because since it's resolved it's done. The better approach here is to use observer-observable pattern for example i wrote following code that observes socket client event. You can extend this code to met your need
const evokeObjectMethodWithArgs = (methodName, args) => (src) => src[methodName].apply(null, args);
const hasMethodName = (name) => (target = {}) => typeof target[name] === 'function';
const Observable = function (fn) {
const subscribers = [];
this.subscribe = subscribers.push.bind(subscribers);
const observer = {
next: (...args) => subscribers.filter(hasMethodName('next')).forEach(evokeObjectMethodWithArgs('next', args))
};
setTimeout(() => {
try {
fn(observer);
} catch (e) {
subscribers.filter(hasMethodName('error')).forEach(evokeObjectMethodWithArgs('error', e));
}
});
};
const fromEvent = (target, eventName) => new Observable((obs) => target.on(eventName, obs.next));
fromEvent(client, 'document:save').subscribe({
async next(document, docName) {
await writeFilePromise(resolve(dataDir, `${docName}`), document);
client.emit('document:save', document);
}
});
If you need to change the return value of promise, simply return new value in then and chain next then/catch on it
var p1 = new Promise((resolve, reject) => { resolve(1) });
var p2 = p1.then(v => {
console.log("First then, value is", v);
return 2;
});
p2.then(v => {
console.log("Second then, value is", v);
});
You can write tests to confirm the behavior.
By running the following test you can conclude that
The resolve()/reject() call never throw error.
Once settled (rejected), the resolved value (rejected error) will be preserved
regardless of following resolve() or reject() calls.
You can also check my blog post for details.
/* eslint-disable prefer-promise-reject-errors */
const flipPromise = require('flip-promise').default
describe('promise', () => {
test('error catch with resolve', () => new Promise(async (rs, rj) => {
const getPromise = () => new Promise(resolve => {
try {
resolve()
} catch (err) {
rj('error caught in unexpected location')
}
})
try {
await getPromise()
throw new Error('error thrown out side')
} catch (e) {
rs('error caught in expected location')
}
}))
test('error catch with reject', () => new Promise(async (rs, rj) => {
const getPromise = () => new Promise((_resolve, reject) => {
try {
reject()
} catch (err) {
rj('error caught in unexpected location')
}
})
try {
await getPromise()
} catch (e) {
try {
throw new Error('error thrown out side')
} catch (e){
rs('error caught in expected location')
}
}
}))
test('await multiple times resolved promise', async () => {
const pr = Promise.resolve(1)
expect(await pr).toBe(1)
expect(await pr).toBe(1)
})
test('await multiple times rejected promise', async () => {
const pr = Promise.reject(1)
expect(await flipPromise(pr)).toBe(1)
expect(await flipPromise(pr)).toBe(1)
})
test('resolve multiple times', async () => {
const pr = new Promise(resolve => {
resolve(1)
resolve(2)
resolve(3)
})
expect(await pr).toBe(1)
})
test('resolve then reject', async () => {
const pr = new Promise((resolve, reject) => {
resolve(1)
resolve(2)
resolve(3)
reject(4)
})
expect(await pr).toBe(1)
})
test('reject multiple times', async () => {
const pr = new Promise((_resolve, reject) => {
reject(1)
reject(2)
reject(3)
})
expect(await flipPromise(pr)).toBe(1)
})
test('reject then resolve', async () => {
const pr = new Promise((resolve, reject) => {
reject(1)
reject(2)
reject(3)
resolve(4)
})
expect(await flipPromise(pr)).toBe(1)
})
test('constructor is not async', async () => {
let val
let val1
const pr = new Promise(resolve => {
val = 1
setTimeout(() => {
resolve()
val1 = 2
})
})
expect(val).toBe(1)
expect(val1).toBeUndefined()
await pr
expect(val).toBe(1)
expect(val1).toBe(2)
})
})
What you should do is put an ng-if on your main ng-outlet and show a loading spinner instead. Once your locale is loaded the you show the outlet and let the component hierarchy render. This way all of your application can assume that the locale is loaded and no checks are necessary.
No. It is not safe to resolve/reject promise multiple times. It is basically a bug, that is hard to catch, becasue it can be not always reproducible.
There is pattern that can be used to trace such issues in debug time. Great lecture on this topic: Ruben Bridgewater — Error handling: doing it right! (the part related to the question is around 40 min)
see github gist: reuse_promise.js
/*
reuse a promise for multiple resolve()s since promises only resolve once and then never again
*/
import React, { useEffect, useState } from 'react'
export default () => {
const [somePromise, setSomePromise] = useState(promiseCreator())
useEffect(() => {
somePromise.then(data => {
// do things here
setSomePromise(promiseCreator())
})
}, [somePromise])
}
const promiseCreator = () => {
return new Promise((resolve, reject) => {
// do things
resolve(/*data*/)
})
}

Categories