尝试用Rust已经有点时间了,之前主要的问题是卡在IndexedDB的使用上,最后的代码是这样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 spawn_local (async move { let (tx, rx) = oneshot::channel::<IdbDatabase>(); let window = web_sys::window ().unwrap (); let idb_factory = window.indexed_db ().unwrap ().unwrap (); let open_request = idb_factory .open_with_u32 (String::from ("todo" ).as_str (), 1 ) .unwrap (); let on_upgradeneeded = Closure::once (move |event: &Event| { let target = event.target ().expect ("Event should have a target; qed" ); let req = target .dyn_ref::<IdbRequest>() .expect ("Event target is IdbRequest; qed" ); let result = req .result () .expect ("IndexedDB.onsuccess should have a valid result; qed" ); assert! (result.is_instance_of::<IdbDatabase>()); let db = IdbDatabase::from (result); let store :IdbObjectStore = db.create_object_store (&String::from ("user" )).unwrap (); let _index = store.create_index_with_str (&String::from ("name" ), &String::from ("name" )).expect ("create_index_with_str error" ); }); open_request.set_onupgradeneeded (Some (on_upgradeneeded.as_ref ().unchecked_ref ())); on_upgradeneeded.forget (); let on_success = Closure::once (move |event: &Event| { let target = event.target ().expect ("Event should have a target; qed" ); let req = target .dyn_ref::<IdbRequest>() .expect ("Event target is IdbRequest; qed" ); let result = req .result () .expect ("IndexedDB.onsuccess should have a valid result; qed" ); assert! (result.is_instance_of::<IdbDatabase>()); let db = IdbDatabase::from (result); let _ = tx.send (db); }); open_request.set_onsuccess (Some (on_success.as_ref ().unchecked_ref ())); on_success.forget (); let db = rx.await .unwrap (); let transaction = db.transaction_with_str_and_mode (&String::from ("user" ), IdbTransactionMode::Readwrite).expect ("transaction_with_str error" ); let store = transaction.object_store (&String::from ("user" )).expect ("store error" ); let name = JsValue::from_str (_content_element.value ().as_str ()); let add_request = store.add_with_key (&name, &JsValue::from ("name" )).expect ("add error" ); let on_add_error = Closure::once (move |event: &Event| { console::log_1 (&String::from ("写入数据失败" ).into ()); console::log_1 (&event.into ()); }); add_request.set_onerror (Some (on_add_error.as_ref ().unchecked_ref ())); on_add_error.forget (); let on_add_success = Closure::once (move |event: &Event| { console::log_1 (&String::from ("写入数据成功" ).into ()); }); add_request.set_onsuccess (Some (on_add_success.as_ref ().unchecked_ref ())); on_add_success.forget (); console::log_1 (&String::from ("do" ).into ()); });
因为IndexedDB连接成功是个异步事件,db
只能在成功事件中才能拿到,最初因为对IndexedDB的使用不熟悉,这玩意在很早以前看HTML5的时候就看到过,不过一直没有用过。我在成功事件中调用create_object_store
,结果给报错了,实际上创建数据库只能在onupgradeneeded
事件中处理。
1 2 let myDatabase = MyDatabase::new ();myDatabase.add (String::from ("jasper" , String::from ("name" )));
早期的想法是把数据库的操作封装起来,但是连接是异步的,上面的add
操作时数据库可能还没有连接成功,db
等于为空。可能是之前做iOS的缘故,我一直想着数据库的操作应该是个单例,不过Rust的单例好像不那么简单,一直报错,所以这个想法就先放放了。
早期的时候甚至不知道如何用Rust设置回调,最后发现了这个库kvdb_web ,代码也是参考了indexed_db.rs
中的方法。这个库并没有演示的例子,所以只是参考了他打开数据库的方法。里面有用到futures::channel
,但是实际使用老是报错。后来我忘了Rust写前端,实际上是运行在Wasm虚拟机的环境下,Rust的多线程则是一般的操作系统的环境下了。实际上要在Wasm使用多线程,得用另外一种方式。实际上是用Rust的代码调用JS了,JS是单线程运行的。所以整个思路虽然是用Rust在写,但实际思想的话得跟着JS来走。我想在后面的代码中拿到db
,有点类似JS中使用await
的方式。最后用了两个库,wasm-bindgen-futures
主要使用了spawn_local
这个东西,另外还有futures-channel
这个库。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 spawn_local (async { let (tx, rx) = oneshot::channel::<i32 >(); let on_success = Closure::once (move |event: &Event| { let target = event.target ().expect ("Event should have a target; qed" ); let req = target .dyn_ref::<IdbRequest>() .expect ("Event target is IdbRequest; qed" ); let result = req .result () .expect ("IndexedDB.onsuccess should have a valid result; qed" ); assert! (result.is_instance_of::<IdbDatabase>()); let db = IdbDatabase::from (result); let _ = tx.send (db); }); open_request.set_onsuccess (Some (on_success.as_ref ().unchecked_ref ())); on_success.forget (); let db = rx.await .unwrap (); })
futures-channel
这个库的用法跟Rust自带那个有点类似,不过只用这个是可以工作的。后面的问题基本上是IndexedDB
的用法问题,最后终于可以把数据成功插入了,虽然再次插入数据时会有写入失败的问题,不过这个已经是后面的事了。
当我费了很多周折,成功写入时,也就是写这篇文章时候,突然间发现可以直接用下面的方式写,如果只是要写入数据的话。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 let window = web_sys::window ().unwrap ();let idb_factory = window.indexed_db ().unwrap ().unwrap ();let open_request = idb_factory .open_with_u32 (String::from ("todo" ).as_str (), 1 ) .unwrap (); let on_upgradeneeded = Closure::once (move |event: &Event| { let target = event.target ().expect ("Event should have a target; qed" ); let req = target .dyn_ref::<IdbRequest>() .expect ("Event target is IdbRequest; qed" ); let result = req .result () .expect ("IndexedDB.onsuccess should have a valid result; qed" ); assert! (result.is_instance_of::<IdbDatabase>()); let db = IdbDatabase::from (result); let store : IdbObjectStore = db.create_object_store (&String::from ("user" )).unwrap (); let _index = store .create_index_with_str (&String::from ("name" ), &String::from ("name" )) .expect ("create_index_with_str error" ); }); open_request.set_onupgradeneeded (Some (on_upgradeneeded.as_ref ().unchecked_ref ())); on_upgradeneeded.forget (); let on_success = Closure::once (move |event: &Event| { let target = event.target ().expect ("Event should have a target; qed" ); let req = target .dyn_ref::<IdbRequest>() .expect ("Event target is IdbRequest; qed" ); let result = req .result () .expect ("IndexedDB.onsuccess should have a valid result; qed" ); assert! (result.is_instance_of::<IdbDatabase>()); let db = IdbDatabase::from (result); let transaction = db .transaction_with_str_and_mode (&String::from ("user" ), IdbTransactionMode::Readwrite) .expect ("transaction_with_str error" ); let store = transaction .object_store (&String::from ("user" )) .expect ("store error" ); let name = JsValue::from_str (_content_element.value ().as_str ()); let add_request = store .add_with_key (&name, &JsValue::from ("name" )) .expect ("add error" ); let on_add_error = Closure::once (move |event: &Event| { console::log_1 (&String::from ("写入数据失败" ).into ()); console::log_1 (&event.into ()); }); add_request.set_onerror (Some (on_add_error.as_ref ().unchecked_ref ())); on_add_error.forget (); let on_add_success = Closure::once (move |event: &Event| { console::log_1 (&String::from ("写入数据成功" ).into ()); }); add_request.set_onsuccess (Some (on_add_success.as_ref ().unchecked_ref ())); on_add_success.forget (); }); open_request.set_onsuccess (Some (on_success.as_ref ().unchecked_ref ())); on_success.forget ();
总结 至此算是把IndexedDB
的问题解决了,如果想把操作封装起来,那么可以用前面的方式。只是简单的操作的话,后面的代码也是可以的。到目前为止,基本上还没有写网页的东西,Yew
的东西,也就只是搭了个架子时有用到,并没有深入的使用。后面得需要好好的深入下,Yew
这个框架跟传统的还不太一样,用trunk serve
启动后,实际会有一个WebSocket
在前后端之间通讯。
再谈谈Rust学习的东西,实际上《Rust权威指南》这本书我只是看了个大概,而目前的实验项目中并没有使用Rust去写很多逻辑的代码,更多的是调用。用Rust写前端这个方式,起步会比一般的难点,主要还是牵涉的东西不只是Rust的问题,还有Wasm以及JS相关的东西,最重要的还是要明白他的运行环境是浏览器中Wasm虚拟机。不过后续如果有大量成熟的库,可以解决这些基础问题的话,只写网页的话,难度会小些。
虽然项目整体进展不大,也走了一些弯路,不过却也让我思考到了更多的东西。现在感觉Rust是未来全栈程序员必备的语言,这门语言是非常考验你是不是一个合格的程序员,因为他既有很底层的东西,比如内存概念,也有很现代的语法,但前提是你需要都懂,对于那些不是科班出身又没有好好学习计算机相关的东西人来说,这个确实有难度。