I have the following code (snippet out of a larger program and within an async function):
foo = questionData.media ? (await new Promise(resolve => {
const image = new Image();
image.onload = function () {
const canvas = document.ceateElement("canvas");
canvas.width = this.width;
canvas.height = this.height;
canvas.drawImage(image, 0, 0, this.width, this.height);
resolve(canvas.toDataURL("image/jpg"));
};
image.src = "https://example.com/" + questionData.media;
})) : false;
I keep getting SyntaxError: Unexpected token 'new' on the first line, and I have no idea why. When I try to use parenthesis:
foo = questionData.media ? (await (new Promise(resolve => {
const image = new Image();
image.onload = function () {
const canvas = document.ceateElement("canvas");
canvas.width = this.width;
canvas.height = this.height;
canvas.drawImage(image, 0, 0, this.width, this.height);
resolve(canvas.toDataURL("image/jpg"));
};
image.src = "https://example.com/" + questionData.media;
}))) : false;
And I get Uncaught ReferenceError: await is not defined.
Meanwhile, I have this code that works just fine:
data.push(await new Promise(resolve => {
const req = new XMLHttpRequest();
req.onreadystatechange = function () {
if (this.readyState === 4 && this.status === 200) {
resolve(this.responseText);
}
};
req.open("GET", `https://example.com/${id}/answer`, true);
req.send();
}));
What am I missing?
It sounds like you aren't in an async function, so await does not get interpreted as a keyword, but as a variable name. If you put the entire initial snippet into an async function, it'll work as expected.
What's going on with
Unexpected token 'new'
and
await is not defined
is that, when you're not in an async function, those words are interpreted as normal identifiers (variable names) - if you switch them out for another placeholder variable name, it makes more sense for why the syntax would be invalid:
foo = questionData.media ? (someVariable new Promise(resolve => {
foo = questionData.media ? (someVariable (new Promise(resolve => {
Neither of those makes sense, and so the parser will give you errors as expected, when you use await when not in an async function.
Related
How can I return true when function is run inside function part of new Image() .onload, in order to verify if a url is a valid image?
var valid = false;
checkImage('https://example.com/image.png')
console.log(valid) //always false at first run
function checkImage(url) {
var image = new Image();
image.onload = function () {
if (this.width > 0) {
valid = true;
}
}
image.onerror = function() {
valid = false;
}
image.src = url;
}
I also tried setting a global variable which doesn't work,Or any other way to return true / false back via checkImage(url) ?
Got this initial solution from https://stackoverflow.com/a/55880263/8719001
(async () => {
let valid = await checkImage('https://example.com/image.png')
console.log(valid)
})();
async function checkImage(url) {
return new Promise(resolve=>{
const image = new Image()
image.onload = () => resolve(!!image.width)
image.onerror = () => resolve(false)
image.src = url
})
}
Following your code example you'll need to wrap your result in a Promise, which is an object made for "returning a result later":
function checkImage(url) {
return new Promise((resolve, reject) => {
var image = new Image();
image.onload = function () {
if (this.width > 0) {
resolve()
} else {
reject()
}
}
image.onerror = reject
image.src = url;
})
}
const valid = await checkImage('https://example.com/image.png')
Alternatively, a simpler way of doing this would be to use fetch if your only goal is to check for the file's existence (and not necessarily checking whether it works as an image):
const exists = await fetch(url, {method: 'HEAD'})
.then(response => response.status === 200)
I am drawing large images (~20 MB in size) on the HTML5 Canvas and creating a small thumbnail out of them. This is how I'm doing this:
const img = new Image();
img.src = '20mb-image.jpg';
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.canvas.width = 240;
ctx.canvas.height = 240;
ctx.drawImage(img, 0, 0, 240, 240);
const base64 = encodeURIComponent(canvas.toDataURL('image/webp', 0.5));
// Do something with the base64
};
While doing this, the page hangs up for about 5 seconds before completely drawing the image on the canvas which is understandable because it is a very large image. So, I tried to find out if I could make use of web workers in this case. I found the function transferControlToOffscreen(), but it seems to have limited support and is even deemed as an experimental feature on MDN.
I was wondering if there was a different way of drawing large images on the canvas without hanging up the page.
Also, while writing this question, one solution I have thought of is to draw the image piecewise from an N x N grid.
createImageBitmap() is supposed to offer you this feature, however only Chrome seems to really do the decoding of the image in parallel...
This method will create an ImageBitmap, readily available in the GPU to be painted by the canvas. So once you get this, you can paint it on a canvas with almost no overhead.
It is somehow expected that to create an ImageBitmap from sources like a canvas, an HTML video element, an ImageData, an ImageBitmap, or even an HTML image, most of the process will be done synchronously, because the source's bitmap can actually change synchronously right after the call.
With a Blob source though, the source's bitmap won't change and browsers can make everything in parallel with no problem.
That's exactly what Chromium browsers do. Unfortunately Safari does everything synchronously, and Firefox does something quite weird where they apparently lock the UI thread but not the CPU one...
// ignite an rAF loop to avoid false signals from "instant refresh ticks"
const rafloop = () => requestAnimationFrame(rafloop);
rafloop();
// start after some time, when the page is well ready
setTimeout(doTest, 250);
const counters = {
tasks: 0,
paintingFrames: 0
};
let stopped = false;
const {port1, port2} = new MessageChannel();
const postTask = (cb) => {
port1.addEventListener("message", () => cb(), {once: true});
port1.start();
port2.postMessage("");
};
function startTaskCounter() {
postTask(check);
function check() {
counters.tasks++;
if (!stopped) postTask(check);
}
}
function startPaintingFrameCounter() {
requestAnimationFrame(check);
function check() {
counters.paintingFrames++;
if (!stopped) requestAnimationFrame(check);
}
}
async function doTest() {
const resp = await fetch("https://upload.wikimedia.org/wikipedia/commons/c/cf/Black_hole_-_Messier_87.jpg?r=" + Math.random());
const blob = await resp.blob();
startPaintingFrameCounter();
startTaskCounter();
const t1 = performance.now();
const bmp = await createImageBitmap(blob);
const t2 = performance.now();
console.log(`decoded in ${t2 - t1}ms`)
const ctx = document.createElement('canvas').getContext('2d');
ctx.drawImage(bmp, 0, 0);
const t3 = performance.now();
console.log(`Drawn in ${t3 - t2}ms`)
console.log(counters);
stopped = true;
}
However all hopes is not gone yet, since it seems that current browsers now all "support" this method from web-Workers, so we can actually generate this bitmap from one, and still use it in the main thread while waiting for better support of the OffscreenCanvas APIs.
Of course, Safari will not make our lives easy and we have to special handle it since it can't reuse a transferred bitmap. (Note that they won't even allow to us to fetch correctly from StackSnippets, but I can't do much about that).
const support_bitmap_transfer = testSupportBitmapTransfer();
const getImageBitmapAsync = async(url) => {
// to reuse the same worker every time, we store it as property of the function
const worker = getImageBitmapAsync.worker ??=
new Worker(URL.createObjectURL(new Blob([`
onmessage = async ({data: {url, canTransfer}, ports}) => {
try {
const resp = await fetch(url);
const blob = await resp.blob();
const bmp = await createImageBitmap(blob);
ports[0].postMessage(bmp, canTransfer ? [bmp] : []);
}
catch(err) {
setTimeout(() => { throw err });
}
};
`], {type: "text/javascript"})));
// we use a MessageChannel to build a "Promising" Worker
const {port1, port2} = new MessageChannel();
const canTransfer = await support_bitmap_transfer;
worker.postMessage({url, canTransfer}, [port2]);
return new Promise((res, rej) => {
port1.onmessage = ({data}) => res(data);
worker.onerror = (evt) => rej(evt.message);
});
};
// [demo only]
// ignite an rAF loop to avoid false signals from "instant refresh ticks"
const rafloop = () => requestAnimationFrame(rafloop);
rafloop();
// start after some time, when the page is well ready
setTimeout(() => doTest().catch(() => stopped = true), 250);
const counters = {
tasks: 0,
paintingFrames: 0
};
let stopped = false;
const {port1, port2} = new MessageChannel();
const postTask = (cb) => {
port1.addEventListener("message", () => cb(), { once: true });
port1.start();
port2.postMessage("");
};
function startTaskCounter() {
postTask(check);
function check() {
counters.tasks++;
if (!stopped) postTask(check);
}
}
function startPaintingFrameCounter() {
requestAnimationFrame(check);
function check() {
counters.paintingFrames++;
if (!stopped) requestAnimationFrame(check);
}
}
async function doTest() {
const url = "https://upload.wikimedia.org/wikipedia/commons/c/cf/Black_hole_-_Messier_87.jpg?r=" + Math.random();
startPaintingFrameCounter();
startTaskCounter();
const t1 = performance.now();
// Basically you'll only need this line
const bmp = await getImageBitmapAsync(url);
const t2 = performance.now();
console.log(`decoded in ${t2 - t1}ms`)
const ctx = document.createElement("canvas").getContext("2d");
ctx.drawImage(bmp, 0, 0);
const t3 = performance.now();
console.log(`Drawn in ${t3 - t2}ms`)
console.log(counters);
stopped = true;
}
// Safari doesn't support drawing back ImageBitmap's that have been transferred
// not transferring these is overkill for the other ones
// so we need to test for it.
// thanks once again Safari for doing things your way...
async function testSupportBitmapTransfer() {
const bmp = await createImageBitmap(new ImageData(5, 5));
const {port1, port2} = new MessageChannel();
const transferred = new Promise((res) => port2.onmessage = ({data}) => res(data));
port1.postMessage(bmp, [bmp]);
try{
document.createElement("canvas")
.getContext("2d")
.drawImage(await transferred);
return true;
}
catch(err) {
return false;
}
}
Or without all the measuring fluff and the Safari special handling:
const getImageBitmapAsync = async(url) => {
// to reuse the same worker every time, we store it as property of the function
const worker = getImageBitmapAsync.worker ??=
new Worker(URL.createObjectURL(new Blob([`
onmessage = async ({data: {url}, ports}) => {
try {
const resp = await fetch(url);
const blob = await resp.blob();
const bmp = await createImageBitmap(blob);
ports[0].postMessage(bmp, [bmp]);
}
catch(err) {
setTimeout(() => { throw err });
}
};
`], {type: "text/javascript"})));
// we use a MessageChannel to build a "Promising" Worker
const {port1, port2} = new MessageChannel();
worker.postMessage({url}, [port2]);
return new Promise((res, rej) => {
port1.onmessage = ({data}) => res(data);
worker.onerror = (evt) => rej(evt.message);
});
};
(async () => {
const url = "https://upload.wikimedia.org/wikipedia/commons/c/cf/Black_hole_-_Messier_87.jpg?r=" + Math.random();
const bmp = await getImageBitmapAsync(url);
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.drawImage(bmp, 0, 0, canvas.width, canvas.height);
})();
<canvas width=250 height=146></canvas>
But notice how just starting a Web Worker is an heavy operation in itself and how it may be a total overkill to use it only for one image. So be sure to reuse this Worker if you need to resize several images.
After reading How do I return the response from an asynchronous call? by Felix Kling, I am still confused about how I can return a value from an asynchronous callback.
My goal: convert a static image to base64 once and store that image in indexDB until indexDB throws some kind of storage error.
I am using this async idb npm module
// init the idb store
const initIDB = async () => {
const db = await openDB('db', 1, {
upgrade(db) {
db.createObjectStore('tempStore', { keyPath: 'id', autoIncrement: true });
},
});
const tx = db.transaction('tempStore', 'readwrite');
await overloadIDB(tx.store);
await tx.done;
return true;
};
// random number generator
const getRandomArbitrary = (min, max) => Math.random() * (max - min) + min;
// function will overload the idb
const overloadIDB = async (store) => {
const imgurl = "someLocalImage.png";
const promises = [];
return toDataURL(imgurl, async (s) => {
for (let i = 0; i < 10; i++) {
if (i > 0 && i % 100 === 0) console.log('A set done');
try {
const num = Math.round(getRandomArbitrary(1, 1000000));
const data = {
id: num,
img: s,
};
store.add(data);
} catch (e) {
console.log(e.toString());
console.dir(e);
break;
}
}
console.log('Done');
});
};
// convert image to base64
const toDataURL = (url, callback) => {
const xhr = new XMLHttpRequest();
xhr.onload = () => {
const reader = new FileReader();
reader.onloadend = () => {
callback(reader.result);
};
reader.readAsDataURL(xhr.response);
};
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.send();
};
Ideally, I would like to return the value from the toDataURL's callback function and use that result in the for loop but I always get undefined which makes sense due to asynchronous behaviour.
The above code fails to execute the transaction store.add(data) multiple times and fails when i = 0.
I have tried wrapping toDataURL with a new Promise(resolve, reject) like so
const toDataURL = (url, callback) => new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.onload = () => {
const reader = new FileReader();
reader.onloadend = () => {
resolve(callback(reader.result));
};
reader.readAsDataURL(xhr.response);
};
xhr.send();
});
and then using Promise.all to resolve an array of stores like so
const overloadIDB = async (store) => {
const imgurl = 'someLocalImage.png';
const promises = [];
return toDataURL(imgurl, async (s) => {
console.log('s :', s);
for (let i = 0; i < 10; i++) {
if (i > 0 && i % 100 === 0) console.log('A set done');
try {
const num = Math.round(getRandomArbitrary(1, 1000000));
const data = {
id: num,
img: s,
};
promises.push(store.add(data));
} catch (e) {
console.log(e.toString());
console.dir(e);
break;
}
}
await Promise.all(promises);
console.log('Done');
});
};
but returns an error Failed to execute 'add' on 'IDBObjectStore': The transaction has finished.
At this point I think I my approach is flawed but I am not sure how I can fix it. Can anyone point to some solution please?
You cannot perform async operations in the middle of indexedDB operations. Perform your fetch entirely, then connect, create a transaction, and store the result.
Let's say I have an array that contains several image URLs
const imgs = [
'https://placehold.it/120x120&text=image1',
'https://placehold.it/120x120&text=image2',
'https://placehold.it/120x120&text=image3'
]
And I want to get those images as base 64 strings on the fly in preparation for a network request. What would I have to do to accomplish this using javascript?
I've currently tried:
const getDataUrl = (url) => {
return new Promise((resolve, reject) => {
let img = new Image()
let canvas = document.createElement('canvas')
img.setAttribute('crossOrigin', 'anonymous')
img.onload = function(){
canvas.height = img.height
canvas.width = img.width
canvas.getContext('2d').drawImage(img, 0, 0)
resolve(canvas.toDataUrl('png'))
}
img.src = url
})
}
let dataUrls = []
for(let img of imgs){
getDataUrl(img).then(res => {
dataUrls.push(res)
})
console.log(dataUrls)
}
But the promise does not wait for the image to load and resolve and I get an empty array
So I then tried a recursive solution:
let dataUrl = ''
const getDataUrl = (url) => {
let img = new Image()
let canvas = document.createElement('canvas')
img.setAttribute('crossOrigin', 'anonymous')
img.onload = function(){
canvas.height = img.height
canvas.width = img.width
canvas.getContext('2d').drawImage(img, 0, 0)
dataUrl = canvas.toDataUrl('png')
}
img.src = url
if(checkIntegrity()) return dataUrl
}
const checkIntegrity = () => {
if(dataUrl.length > 0){
return true
}else{
return checkIntegrity()
}
}
which is unsavory because I had to rely on dataUrl being in the global scope and didn't work anyway because I get a too much recursion error when I run this.
Finally, I thought I could try predefining the onLoad function and passing the resolve function as an argument to that:
const onLoad = (img, resolve) => {
let canvas = document.createElement('canvas')
canvas.height = img.height
canvas.width = img.width
canvas.getContext('2d').drawImage(img, 0, 0)
resolve(canvas.toDataURL('png'))
}
const getDataUrl = (url) => {
return new Promise((resolve, reject) => {
let img = new Image()
img.setAttribute('crossOrigin', 'anonymous')
img.onload = onLoad(img, resolve)
img.src = url
})
}
for(let img of imgs){
getDataUrl(img).then(res => {
console.log(res)
dataUrls.push(res)
})
}
console.log(dataUrls)
which was my most successfull attempt, but it ends up returning data:, from every call so it isn't working either. Here is a fiddle with that code: https://jsfiddle.net/5o4Lq3bh/34/
Barring this, I'm at my wit's end. I also tried manipulating dom mounted images instead of javascript image objects and using a counter with a loaded check and a different recursive function but I get the same error, too much recursion.
My main issue seems to be the iteration. I am pretty sure for / of is synchronous, so I'm guessing the issue is that I can't pass around resolve willy nilly to other functions and expect to get valid results.
This would be easy to do on load but it has to happen on the fly, any help is appreciated.
The first snippet and the third snippet you shared has some issues.
First snippet, your console.log will always print an empty array because it's outside the then statement.
for(let img of imgs){
getDataUrl(img).then(res => {
dataUrls.push(res)
})
console.log(dataUrls) // will always print [] because this statement should be within then function
}
Third snippet, you are calling onLoad function, instead of passing it as a event handler
img.onload = onLoad(img, resolve) // this will call onLoad, this is not an event handler
The below code works, but this doesn't utilize the full feature of promises
const getDataUrl = (url) => {
return new Promise((resolve, reject) => {
let img = new Image()
img.setAttribute('crossOrigin', 'anonymous')
img.onload = function(){
let canvas = document.createElement('canvas')
canvas.height = img.height
canvas.width = img.width
canvas.getContext('2d').drawImage(img, 0, 0)
let b64String = canvas.toDataURL('png');
resolve(b64String)
}
img.src = url
})
}
for(let img of imgs){
getDataUrl(img).then(res => {
dataUrls.push(res)
console.log(dataUrls.length)
})
}
If you want a much clear approach then use the below
const imgs = [
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/pie.png',
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/pie.png',
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/3/pie.png'
]
let dataUrls = []
let images = [];
for(let imgUrl of imgs){
images.push(new Promise((resolve, reject) => {
let img = new Image();
img.setAttribute('crossOrigin', 'anonymous')
img.onload = function(){
let canvas = document.createElement('canvas')
canvas.height = img.height
canvas.width = img.width
canvas.getContext('2d').drawImage(img, 0, 0)
let b64String = canvas.toDataURL('png');
dataUrls.push(b64String);
resolve(b64String)
}
img.src = imgUrl
}));
}
Promise.all(images).then(function(){
console.log(dataUrls)
})
https://jsfiddle.net/karthick6891/5o4Lq3bh/52/
Note: FYI, your fiddle didn;t work for me too, I had the same cross origin issue like everyone has posted.
This app https://developer.mozilla.org/en-US/demos/detail/meet-me-there/launch accepts uploaded photos, attaches a qr code to them, and then allows you to share them. I've attached below the JavaScript for everything except the QR functionality. The app doesn't use jQuery, but at the beginning it assigns a function to the $.
window.onload = function(){
var $ = function(id){
console.log(id);
return document.getElementById(id);
},
When I run the app with the console.log in the above position, it shows that quite a few 'ids' are getting passed through that console.log(id). As the file loads, it logs 'surface', 'cam' and 'upload' and, as you engage the app, it logs 'result', 'sharer', 'uploadedURL' and many others. Problem is that I don't see how everything keeps getting passed through that function for the console.log to log the different 'id's at that point in the code. Therefore, I wonder what the significance of '$' is in this context (no jQuery). Specifically, by creating that '$' function, is it called anytime that any of the other events with $ are run, such as $('upload').onclick = function(){...
Is it working similar to how adding a prototype is working in jquery by using $.prototype.function() in jquery. If so, where does it get this functionality from if there's no jQuery.
window.onload = function(){
var $ = function(id){
console.log(id);
return document.getElementById(id);
},
canvas = $('surface'),
ctx = canvas.getContext('2d'),
watcher, loc='No location provided', located;
$('cam').onchange = function(event){
console.log(event);
console.trace();
var files = event.target.files,
file;
if (files && files.length > 0) {
file = files[0];
try {
var URL = window.URL || window.webkitURL || window.mozURL;
var imgURL = URL.createObjectURL(file);
var img = new Image();
img.id = "tester";
//Load it onto the canvas
img.onload = function() {
console.trace();
canvas.width = this.width;
canvas.height = this.height;
$('info').innerHTML = ("Width: " + this.width + "px, Height: " + this.height + "px");
$('result').width = 400;
$('result').height = (400 / (this.width/this.height)) >> 0;
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
var codeSize = (canvas.height/4) >> 0;
var imgn = new Image();
imgn.onload = function(){
ctx.drawImage(imgn, (canvas.width-5- codeSize), (canvas.height-5-codeSize), codeSize, codeSize);
$('result').src = canvas.toDataURL();
}
imgn.src = (QR.encode(loc, codeSize, 5));
}
img.src = imgURL;
} catch (e) {
console.log("error: " + e);
}
}
},
// borrowed this functionality from cgack's demo
// https://developer.mozilla.org/en-US/demos/detail/snap-and-share
$('upload').onclick = function(){
$('infomsg').style.display = 'block';
var url = "http://api.imgur.com/2/upload.json",
params = new FormData(),
http = new XMLHttpRequest();
params.append('key','29a8b1db1d8fda0df87006def2307242');
params.append('image',canvas.toDataURL().split(',')[1]);
http.open("POST", url);
http.onload = function() {
var url = JSON.parse(http.responseText).upload.links.imgur_page;
$('uploadedUrl').href = url;
$('uploadedUrl').innerHTML = url;
$('shareFb').href = ("http://www.facebook.com/sharer.php?u="+url);
$('shareTwitter').href = ("http://twitter.com/intent/tweet?url="+url);
$('sharer').style.display = 'block';
$('infomsg').style.display = 'none';
};
http.send(params);
console.log(params);
};
watcher = navigator.geolocation.watchPosition(function(position){
console.trace();
console.log("navigator");
loc = "geo:" + position.coords.latitude + "," +position.coords.longitude;
located = true;
}, function(error){
if(error.code == error.PERMISSION_DENIED || error.code == error.POSITION_UNAVAILABLE)
alert('damn, we were not able to locate you. sorry.');
}
);
};
$ is just a variable name, like any other. It has no special meaning.
"Problem is that I don't see how everything keeps getting passed through that function for the console.log to log the 'id' at that point in the code."
Any time you see $, it's a reference to the function. So a () after it invokes it with the given argument. It's just like any other function, just with a funny name referencing it.
"Therefore, I wonder what the significance of '$' is in this context (no jQuery). Specifically, by creating that '$' function, is it called anytime that any of the other events with $ are run"
Again, no real significance. It's just a function name. If you renamed all the instances of $ to byId, it would behave the same.
window.onload = function(){
// var $ = function(id){
var byId = function(id){
console.log(id);
return document.getElementById(id);
},
canvas = foo('surface'),
ctx = canvas.getContext('2d'),
watcher, loc='No location provided', located;
byId('cam').onchange = function(event){
/* a bunch of code */
},
byId('upload').onclick = function(){
/* a bunch of code */
};
// rest of the code
};