Javascript承诺+;useState+;火箭筒
我的代码中有一个数据库侦听器,我试图获取每个用户的新帖子,然后(当我将所有帖子都放在一个数组中时)更新帖子状态 我的代码看起来像这样,但工作不好,因为setPosts是异步的,有时在结束状态更新之前可能会再次调用它。我认为我需要用承诺来包装侦听器,但我不知道如何在组件卸载时分离侦听器Javascript承诺+;useState+;火箭筒,javascript,reactjs,react-native,google-cloud-firestore,es6-promise,Javascript,Reactjs,React Native,Google Cloud Firestore,Es6 Promise,我的代码中有一个数据库侦听器,我试图获取每个用户的新帖子,然后(当我将所有帖子都放在一个数组中时)更新帖子状态 我的代码看起来像这样,但工作不好,因为setPosts是异步的,有时在结束状态更新之前可能会再次调用它。我认为我需要用承诺来包装侦听器,但我不知道如何在组件卸载时分离侦听器 useEffect(() => { const { firebase } = props; // Realtime database listener const unsuscrib
useEffect(() => {
const { firebase } = props;
// Realtime database listener
const unsuscribe = firebase
.getDatabase()
.collection("posts")
.doc(firebase.getCurrentUser().uid)
.collection("userPosts")
.onSnapshot((snapshot) => {
let changes = snapshot.docChanges();
changes.forEach(async (change) => {
if (change.type === "added") {
// Get the new post
const newPost = change.doc.data();
// TODO - Move to flatlist On end reached
const uri = await firebase
.getStorage()
.ref(`photos/${newPost.id}`)
.getDownloadURL();
// TODO - Add the new post *(sorted by time)* to the posts list
setPosts([{ ...newPost, uri }, ...posts]);
}
});
});
/* Pd: At the first time, this function will get all the user's posts */
return () => {
// Detach the listening agent
unsuscribe();
};
}, []);
有什么想法吗
此外,我还想做:
useEffect(() => {
const { firebase } = props;
let postsArray = [];
// Realtime database listener
const unsuscribe = firebase
.getDatabase()
.collection("posts")
.doc(firebase.getCurrentUser().uid)
.collection("userPosts")
.orderBy("time") // Sorted by date
.onSnapshot((snapshot) => {
let changes = snapshot.docChanges();
changes.forEach(async (change) => {
if (change.type === "added") {
// Get the new post
const newPost = change.doc.data();
// Add the new post to the posts list
postsArray.push(newPost);
}
});
setPosts(postsArray.reverse());
});
但是在本例中,post uri也保存在firestore文档中(我可以这样做,因为我在firestore上使用了一个从存储中获取post的云函数),我不知道这是否是一个好的做法
谢谢
更新
云功能代码:
exports.validateImageDimensions = functions
.region("us-central1")
.runWith({ memory: "2GB", timeoutSeconds: 120 })
.https.onCall(async (data, context) => {
// Libraries
const admin = require("firebase-admin");
const sizeOf = require("image-size");
const url = require("url");
const https = require("https");
const sharp = require("sharp");
const path = require("path");
const os = require("os");
const fs = require("fs");
// Lazy initialization of the Admin SDK
if (!is_validateImageDimensions_initialized) {
const serviceAccount = require("./serviceAccountKey.json");
admin.initializeApp({
// ...
});
is_validateImageDimensions_initialized = true;
}
// Create Storage
const storage = admin.storage();
// Create Firestore
const firestore = admin.firestore();
// Get the image's owner
const owner = context.auth.token.uid;
// Get the image's info
const { id, description, location, tags } = data;
// Photos's bucket
const bucket = storage.bucket("bucket-name");
// File Path
const filePath = `photos/${id}`;
// Get the file
const file = getFile(filePath);
// Check if the file is a jpeg image
const metadata = await file.getMetadata();
const isJpgImage = metadata[0].contentType === "image/jpeg";
// Get the file's url
const fileUrl = await getUrl(file);
// Get the photo dimensions using the `image-size` library
getImageFromUrl(fileUrl)
.then(async (image) => {
// Check if the image has valid dimensions
let dimensions = sizeOf(image);
// Create the associated Firestore's document to the valid images
if (isJpgImage && hasValidDimensions(dimensions)) {
// Create a thumbnail for the uploaded image
const thumbnailPath = await generateThumbnail(filePath);
// Get the thumbnail
const thumbnail = getFile(thumbnailPath);
// Get the thumbnail's url
const thumbnailUrl = await getUrl(thumbnail);
try {
await firestore
.collection("posts")
.doc(owner)
.collection("userPosts")
.add({
id,
uri: fileUrl,
thumbnailUri: thumbnailUrl, // Useful for progress images
description,
location,
tags,
date: admin.firestore.FieldValue.serverTimestamp(),
likes: [], // At the first time, when a post is created, zero users has liked it
comments: [], // Also, there aren't any comments
width: dimensions.width,
height: dimensions.height,
});
// TODO: Analytics posts counter
} catch (err) {
console.error(
`Error creating the document in 'posts/{owner}/userPosts/' where 'id === ${id}': ${err}`
);
}
} else {
// Remove the files that are not jpeg images, or whose dimensions are not valid
try {
await file.delete();
console.log(
`The image '${id}' has been deleted because it has invalid dimensions.
This may be an attempt to break the security of the app made by the user '${owner}'`
);
} catch (err) {
console.error(`Error deleting invalid file '${id}': ${err}`);
}
}
})
.catch((e) => {
console.log(e);
});
/* ---------------- AUXILIAR FUNCTIONS ---------------- */
function getFile(filePath) {
/* Get a file from the storage bucket */
return bucket.file(filePath);
}
async function getUrl(file) {
/* Get the public url of a file */
const signedUrls = await file.getSignedUrl({
action: "read",
expires: "01-01-2100",
});
// signedUrls[0] contains the file's public URL
return signedUrls[0];
}
function getImageFromUrl(uri) {
return new Promise((resolve, reject) => {
const options = url.parse(uri); // Automatically converted to an ordinary options object.
const request = https.request(options, (response) => {
if (response.statusCode < 200 || response.statusCode >= 300) {
return reject(new Error("statusCode=" + response.statusCode));
}
let chunks = [];
response.on("data", (chunk) => {
chunks.push(chunk);
});
response.on("end", () => {
try {
chunks = Buffer.concat(chunks);
} catch (e) {
reject(e);
}
resolve(chunks);
});
});
request.on("error", (e) => {
reject(e.message);
});
// Send the request
request.end();
});
}
function hasValidDimensions(dimensions) {
// Posts' valid dimensions
const validDimensions = [
{
width: 1080,
height: 1080,
},
{
width: 1080,
height: 1350,
},
{
width: 1080,
height: 750,
},
];
return (
validDimensions.find(
({ width, height }) =>
width === dimensions.width && height === dimensions.height
) !== undefined
);
}
async function generateThumbnail(filePath) {
/* Generate thumbnail for the progressive images */
// Download file from bucket
const fileName = filePath.split("/").pop();
const tempFilePath = path.join(os.tmpdir(), fileName);
const thumbnailPath = await bucket
.file(filePath)
.download({
destination: tempFilePath,
})
.then(() => {
// Generate a thumbnail using Sharp
const size = 50;
const newFileName = `${fileName}_${size}_thumb.jpg`;
const newFilePath = `thumbnails/${newFileName}`;
const newFileTemp = path.join(os.tmpdir(), newFileName);
sharp(tempFilePath)
.resize(size, null)
.toFile(newFileTemp, async (_err, info) => {
// Uploading the thumbnail.
await bucket.upload(newFileTemp, {
destination: newFilePath,
});
// Once the thumbnail has been uploaded delete the temporal file to free up disk space.
fs.unlinkSync(tempFilePath);
});
// Return the thumbnail's path
return newFilePath;
});
return thumbnailPath;
}
});
exports.validateImageDimensions=函数
.地区(“美国中部1”)
.runWith({内存:“2GB”,超时秒:120})
.https.onCall(异步(数据、上下文)=>{
//图书馆
const admin=require(“firebase管理员”);
const sizeOf=require(“图像大小”);
const url=require(“url”);
const https=require(“https”);
常量夏普=要求(“夏普”);
常量路径=要求(“路径”);
常数os=要求(“os”);
常数fs=要求(“fs”);
//ADMINSDK的延迟初始化
如果(!是否已初始化validateImageDimensions){
const servicecomport=require(“./servicecomportkey.json”);
admin.initializeApp({
// ...
});
is\u validateImageDimensions\u initialized=true;
}
//创建存储
const storage=admin.storage();
//创建Firestore
const firestore=admin.firestore();
//获取图像的所有者
const owner=context.auth.token.uid;
//获取图像的信息
const{id,description,location,tags}=数据;
//照片的水桶
const bucket=storage.bucket(“bucket name”);
//文件路径
const filePath=`photos/${id}`;
//获取文件
const file=getFile(filePath);
//检查文件是否为jpeg图像
const metadata=wait file.getMetadata();
const isJpgImage=元数据[0]。contentType==“image/jpeg”;
//获取文件的url
const fileUrl=wait getUrl(文件);
//使用“图像大小”库获取照片尺寸
getImageFromUrl(文件URL)
。然后(异步(图像)=>{
//检查图像是否具有有效的尺寸
让尺寸=sizeOf(图像);
//创建与有效图像关联的Firestore文档
if(isJpgImage和hasValidDimensions(尺寸)){
//为上传的图像创建缩略图
const thumbnailPath=等待生成缩略图(filePath);
//获取缩略图
const thumbnail=getFile(thumbnailPath);
//获取缩略图的url
const thumbnailUrl=wait getUrl(缩略图);
试一试{
等待火库
.收集(“员额”)
.doc(所有者)
.collection(“用户帖子”)
.添加({
身份证件
uri:fileUrl,
thumbnailUri:thumbnailUrl,//对进度图像有用
描述
位置,
标签,
日期:admin.firestore.FieldValue.serverTimestamp(),
喜欢:[],//第一次创建帖子时,没有用户喜欢它
注释:[],//另外,没有任何注释
宽度:尺寸。宽度,
高度:尺寸。高度,
});
//TODO:分析帖子计数器
}捕捉(错误){
控制台错误(
`在'posts/{owner}/userPosts/'中创建文档时出错,其中'id===${id}':${err}`
);
}
}否则{
//删除非jpeg图像或尺寸无效的文件
试一试{
等待文件。删除();
console.log(
`映像“${id}”已被删除,因为它的维度无效。
这可能是试图破坏由用户“${owner}”创建的应用程序的安全性`
);
}捕捉(错误){
错误(`error删除无效文件'${id}':${err}`);
}
}
})
.catch((e)=>{
控制台日志(e);
});
/*------------辅助功能------------------*/
函数getFile(文件路径){
/*从存储桶中获取文件*/
返回bucket.file(filePath);
}
异步函数getUrl(文件){
/*获取文件的公共url*/
const signedUrls=await file.getSignedUrl({
行动:“阅读”,
到期日期:“01-01-2100”,
});
//signedUrls[0]包含文件的公共URL
返回签名URL[0];
}
函数getImageFromUrl(uri){
返回新承诺((解决、拒绝)=>{
const options=url.parse(uri);//自动转换为普通选项对象。
const request=https.request(选项,(响应)=>{
如果(response.statusCode<200 | | response.statusCode>=300){
返回拒绝(新错误(“statusCode=“+response.statusCode”);
}
让chunks=[];
响应.on(“数据”,(块)=>{
推(chunk);
});
响应。在(“结束”、()=>{
试一试{
chunks=Buffer.concat(chunks);
}捕获(e){
拒绝(e);
}
分解(块);
});
});
请求.on(“错误”,(e)=>{
拒绝(e.message);
});
//发送请求
request.end();
});
}
函数hasValidDimensions(尺寸){
//帖子的有效维度
常数有效尺寸=[
{
宽度:1080,
身高:1080,
},
{
宽度:1080,
身高:1350,