How to create a function with a Promise and return data to be used elsewhere?

I am trying to create a function which can perform an insert and return the response object from MongoDB, so I can take the _id of the document and use it in another function.

What I am trying to do is multiple awaits.

let {insertId: userId } = await newUser(...)
let {insertId: telId} = await assignTelelephone({userId: userId, .... })
let {... } = await updateUser({_id: userId, ... } )

But although the function does work, the return is undefined. I expect the function to return an Ok(dataObject) or an Error(errorMessage). What am I missing?

}                                                                                           
module ClientInstance = {                                                                   
  type t                                                                                    
  @send external db: (t, string) => Db.t = "db"                                             
}                                                                                           
module Client = {                                                                           
  type t                                                                                    
  @module("mongodb") @new external make: string => t = "MongoClient"                        
  @send external connect: t => Promise.t<ClientInstance.t> = "connect"                      
  @send external close: t => unit = "close"  
}                                                                                           
                                                            
let uri = "mongodb://localhost:27017"                       
let dbName = "rescript"                                     
                                                            
// The user I want to insert                                
type user = {firstName: string, lastName: string, isLearningRescript: bool}                               
let alex = {firstName: "Alex", lastName: "Kleydints", isLearningRescript: true}  
                                                            
/* The function to insert one user. Takes in a user and returns an object of                              
 type insertResponse                                                                                      
 */  
let insertOne = (user: user) => {                                                                         
  open Promise                                                                                            
  let client = Client.make(uri)                                                                           
  client                                                                                                  
  ->Client.connect                                                                                        
  ->then(instance => {                                                                                    
    instance->ClientInstance.db(dbName)->Db.collection("users")->resolve                                  
  })                  
  ->then(collection => {                                                                                  
    collection->Collection.insertOne(user)->resolve                
  })                                                               
  ->thenResolve(result => {  
    Js.log2("thenResolve", result)  
    result->Ok                  
  })                               
  ->catch(e => {                                                                                          
    let errorMsg = switch e {                                                                             
    | JsError(obj) =>                                              
      switch Js.Exn.message(obj) {                                             
      | Some(msg) => msg                                                                                  
      | None => "Unknown Js Error"                                                                        
      }                                                                                                   
    | _ => "Unknown non-JS error"                                                                         
    }                                                                                                     
    Js.log(errorMsg)                                                                                      
    errorMsg->Error->resolve                
  })                                                                                                  
  ->finally(() => {                                                                                       
    client->Client.close                                                                                  
  })                                                                                                      
  ->ignore                                                                                                
}                                                                                                         
                                                                                                      
let result = insertOne(alex)                                                                          
Js.log2("result", result)  

It compiles and when I run it it gives:

~/rescript/rescript-play/src$ node Mongo.bs.js                                           
result undefined                                                                                          
thenResolve {                                                                                             
  acknowledged: true,                                                                                     
  insertedId: new ObjectId("61027bfff749b20c30076abf")                                                    
}     

Thank you in advance.

If you didn’t use ignore, your function would return a Promise.t<result<...>>. With ignore, it does what you’ve told it to do: ignore the promise and return undefined.

The team always recommends checking your JS output when in doubt, and I think it could help you here. Try it with and without ignore and see the difference.

2 Likes

Thanks @hoichi . Is this the correct practice then?

let insertOne = (user: user) => {                                                                         
  open Promise                                                                                            
  let client = Client.make(uri)                                                                           
  client                                                                                                  
  ->Client.connect                                                                                        
  ->then(instance => {                                                                                    
    instance->ClientInstance.db(dbName)->Db.collection("users")->resolve                            
  })                                                                                                      
  ->then(collection => {                                                                            
    collection->Collection.insertOne(user)->resolve                                                 
  })                                                                                                
  ->thenResolve(result => {                                                                         
    Js.log2("thenResolve", result)                                                                  
    result->Ok                                                                                      
  })                                                                                                      
  ->catch(e => {                                                                                          
    let errorMsg = switch e {                                                                       
    | JsError(obj) =>                                                                               
      switch Js.Exn.message(obj) {                                                                        
      | Some(msg) => msg                                                                                  
      | None => "Unknown Js Error"                                                                        
      }                                                                                                   
    | _ => "Unknown non-JS error"                                                                         
    }                                                                                                     
    Js.log(errorMsg)                                                                                
    errorMsg->Error->resolve                                                                          
  })                                                                                                      
  ->finally(() => {                                                                                       
    client->Client.close                                                                                  
  })                                                                                                      
}                                                                                                         
                                                                                                      
open Promise  
insertOne(alex)
->then(result => {
  Js.log2("result", result)
  switch result {
  | Ok(data) => Js.log2("ok after switch", data)
  | Error(msg) => Js.log2("error after switch", msg)
  }->resolve
})                                                                              
->ignore      
1 Like

EDIT: forEach doesn’t exist in @ryyppy/promise, I must be imagining things.

I think it is, more or less. If you use @ryyppy/promise you could simplify the usage by using forEach:

insertOne(alex)
->Promise.forEach(result => {
  Js.log2("result", result)
  switch result {
  | Ok(data) => Js.log2("ok after switch", data)
  | Error(msg) => Js.log2("error after switch", msg)
  }
})    

And if you want to use result a lot you can look at reason-promise, even though there’s no consensus* on whether using promises of results is a good idea or you’re better of just using reject and catch for all your error handling.


* Here’s a wider discussion, if you don’t have anything better to do :slightly_smiling_face:

1 Like

I want to return the result from the insertOne. Can I rewrite this

into

let (data, msg) = insertOne(alex)
->Promise.forEach(result => {
switch result {
| Ok(data) => data
| Error(msg) => msg
}
})

Js.log2("the data", data)

I need to use the data in a different function. The thing is that I want to run a query, get a result, and based on that result run a different query, and based on that result do some other things.

In Javascript i do something like:

let userId 

const {_id: foundUserId} = await db.users.findOne({name:"Inco"})
if (!foundUserId) {
// create a new user and assign the newUserId to userId
const {_id: newUserId } = await db.users.insertOne({name: "Inco"})
userId = newUserId
} else {
userId = foundUserId
}
// Now I can user the userId in other queries
const result = await db.keys.insertOne({pinCode: "1234", userId: userId})
// ... and so on

Help would be appreciated.

Hi @el1t1st, here’s a simplified version which doesn’t use the result type.

Using some simplified bindings for the example:

module User = {
  type t = {
    _id: string,
    name: string,
  }
  @val external findOne: {..} => Promise.t<option<t>> = "findOne"
  @val external insertOne: {..} => Promise.t<t> = "insertOne"
}

module Keys = {
  type t = {_id: string}
  @val external insertOne: {..} => Promise.t<t> = "insertOne"
}

Some utility functions:

let findUser = (name: string) => {
  User.findOne({"name": name})->Promise.then(result => {
    switch result {
    | None => User.insertOne({"name": name})
    | Some(user) => Promise.resolve(user)
    }
  })
}

let insertKey = (user: User.t, pinCode: string) => {
  Keys.insertOne({"userId": user._id, "pinCode": pinCode})
}

Then you can write:

findUser("Inco")
->Promise.then(user => {
  insertKey(user, "1234")->Promise.thenResolve(key => {
    Js.log2("Inserted key ", key._id)
  })
})
->Promise.catch(_ => {
  // Handle any errors
  Promise.resolve()
})
->ignore

Not tested, but would something like that work for you?

1 Like

I don’t see the forEach() function in the rescript-promise library. Just wondering where you might be seeing that? Thanks.

1 Like

Yeah, I’m not seeing it either, must be confusing it with some other lib :man_facepalming:

I think it could be useful though.

1 Like