无法写入 .then() 内的文件?

Cant write to files inside of .then()?

我对我之前工作的一些代码有疑问,但现在决定它根本不想工作。基本上,我承诺 returns 给我一组数据。我会在代码底部解释发生了什么。



function mkdir(path){
    return new Promise(function(resolve,reject){
        fs.mkdir(path, { recursive: true },function(err){
            if(err){console.log(err)}
        })
        return resolve()
    })
}
function writeFile(file,content){
    return new Promise(function(resolve,reject){
        fs.writeFile(file, content, function(err){
            if(err){console.log(err)}
            return resolve()
        })
    })
}


function rawData(socket){
    var mintDataArray = []
    return new Promise(function(){
        for (let i = 1; i < 13; i+= 1) {
            getdat(i).then(function(r){
                //mintDataArray.push(r)
                for(o in r){
                    var dataurls = []
                    dataurls.push(r[o].discord_url,r[o].magic_eden_url,r[o].twitter_url)

                    //socket.emit('twitterFollowers',r[o])
                    const ProjectData = {
                        "mintDate": 0 || "",
                        "name":"",
                        "stock":0,
                        "links":[],
                        "mintTime": 0 || "",
                        "PricePlusDescription":0 || ""
                    }

                    if(r[o].mintDate != null){
                        ProjectData.mintDate = moment.utc(r[o].mintDate).format("MMMM Do")
                    }else{
                        ProjectData.mintDate = "No date specified yet"
                    }
                    

                    ProjectData.name = r[o].name
                    ProjectData.stock = r[o].supply
                    ProjectData.links.push(dataurls) 
                    ProjectData.PricePlusDescription = r[o].price
                    mintDataArray.push(ProjectData)

                }
                
                
            }).then(function(socket){
                //CollectionSorter(mintDataArray)
                for(i in mintDataArray){
                    var data = mintDataArray[i]
                    //console.log(data) // <----- This prints out the data that needs to be written to files.
                    var MintDateFolder = __dirname + "/UpcomingCollections/" +data.mintDate
            
                    if(!fs.existsSync(MintDateFolder)){
                        console.log('huh?')
                        mkdir(__dirname + '/UpcomingCollections/'+data.mintDate)
                    }
                    
                    writeFile(MintDateFolder +"/"+data.name,JSON.stringify(data))
                }


            })
        }
        //socket.emit('twitterFollowers',mintDataArray)
    })
}

所以代码应该做的是检查该目录是否首先存在。如果没有,则创建新目录。然后,它应该将文件写入其中(不仅是该目录,还包括其他目录)。如果目录不存在,它不会创建目录,如果我手动创建目录,它甚至不会写入它,但是它会写入其他目录。我真的不确定这个,因为我之前有这个工作,如果目录不存在,它会在其中创建目录。所以我不确定我搞砸了什么。

我最近制作了 mkdir 和 writefile 函数,我认为它们是问题所在,因为当我进行这项工作时,我只是在使用 fs.mkdir 和 fs.writefile。但是,我在没有这些功能的情况下再次尝试,但我仍然遇到同样的麻烦。我考虑过再次承诺检查目录是否存在,但我已经有相当多的嵌套承诺。

读取()函数:

function read(i){
    return new Promise(function(resolve,reject){
        var r = https.request(options, function (res) {
            var data = []
            res.on('data', function (d) {
                data.push(d)
            }).on('end', function () {
                var NFTTokenData = []
                console.log(`STATUS: ${res.statusCode}`);
                var info = Buffer.concat(data)
                zlib.gunzip(info,function(err,buf){
                    var NFTData = []
                    var x = buf.toString()
                    var dat = JSON.parse(x)
                    var collectionList = dat.pageProps.__APOLLO_STATE__
                    for(keys in collectionList){
                        if(collectionList[keys].__typename.includes('Nft')){
                            collections.push(collectionList[keys])
                            resolve(collections)
                            
                        }
                        
                    }
                })
            
            })
        })
        r.end()
    })
}

最终解决方案

function Project(fromApi) {
  return {
    mintDate: moment.utc(fromApi.mintDate).format("MMMM Do"),
    name: fromApi.name,
    imageLink:fromApi.project_image_link,
    stock: fromApi.supply,
    links: [
      fromApi.discord_url,
      fromApi.magic_eden_url,
      fromApi.twitter_url
    ],
    price: fromApi.price
  }
}

function write(project, dir) {
  const dest = resolve(dir, join(project.mintDate, project.name))
  console.log(dest)
  return stat(dirname(dest))
    .catch(_ => mkdir(dirname(dest), {recursive: true}))
    .then(_ => writeFile(dest, JSON.stringify(project)))
    .then(_ => project)
}

function rawData(socket){
  return new Promise(function(resolve,reject){
    [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17].map(i => 
      read(i).then(function(r){
      var data = []
      for(i in r){
        if(r[i].__typename == "Nft"){
          data.push(r[i])
        }
      }
      data.map(item => {
        var project = Project(item)
        write(project,"./UpcomingCollections")
      })

      })
    ) 
  })
}

这是对下面答案的修改!

您可以在 .then 中做 任何事情 – 它的唯一作用是 序列 函数。

让我们谈谈您代码中的一些其他问题 -

function mkdir(path){
    return new Promise(function(resolve,reject){
        fs.mkdir(path, { recursive: true },function(err){
            if(err){console.log(err)} // ❌ reject(err); do not log
        })
        return resolve() // ❌ resolve() outside of mkdir callback
    })
}

function writeFile(file,content){
    return new Promise(function(resolve,reject){
        fs.writeFile(file, content, function(err){
            if(err){console.log(err)} // ❌ reject(err); do not log
            return resolve() // ⚠️ "return" not needed
        })
    })
}

您是否觉得为 Node 中的每个 fs 函数实现包装器很乏味?您会很高兴知道 fs/promises 模块已经为每个 -

提供了 Promise-based API
import { mkdir, writeFile } from "fs/promises" // ✅

// mkdir(path[, options]) 
// Returns: <Promise> Upon success, fulfills with undefined if recursive is false, or the first directory path created if recursive is true.

// writeFile(file, data[, options])
// Returns: <Promise> Fulfills with undefined upon success.

接下来我们再看看主程序中的其他问题-

function rawData(socket){
  var mintDataArray = [] // ⚠️ state "outside" of Promise context
  return new Promise(function(){
    for (let i = 1; i < 13; i+= 1) {
      getdat(i).then(function(r){
        for(o in r){ // ⚠️ global "o"; don't use for..in, use for..of instead
            // ,,,
            mintDataArray.push(Project) // ⚠️ attempting to send state "out" of Promise

        }
        // ⚠️ missing "return"
        // implicitly returning "undefined"
        
      }).then(function(socket){ // ⚠️ function parameter (socket) receives resolved Promise
        for(i in mintDataArray){ // ⚠️ global "i", for..in again, accessing external state
          var data = mintDataArray[i]
          var MintDateFolder = __dirname + "/UpcomingCollections/" +data.mintDate // ⚠️ use "path" module
  
          if(!fs.existsSync(MintDateFolder)){ // ⚠️ async methods until this point, why Sync here?
              console.log('huh?')
              mkdir(__dirname + '/UpcomingCollections/'+data.mintDate) // ⚠️ asynchronous
          }
          
          writeFile(MintDateFolder +"/"+data.name,JSON.stringify(data)) // ⚠️ attempts write before mkdir completes
        }
      })
    }
    socket.emit('twitterFollowers',mintDataArray) // ⚠️ accessing external state
  })
}

读取和转换数据

工作量很大,但别担心。通过将大问题分解成小问题,我们可以更聪明地工作,而不是更努力地工作。我们首先将 getdat 重命名为 read -

read(i).then(r => {
  const mintData = [] // state *inside* promise context
  for (const v of r) {
    // for all v of r, add project data to mintData
    mintData.push({
      mintDate: v.mintDate,
      name: v.name,
      stock: v.supply,
      links: [
        v.discord_url,
        v.magic_eden_url,
        v.twitter_url
      ],
      price: v.price
    })
  }
  return mintData // "return" resolves the promise
}).then(...)

我们的 .then 功能已经变大了。有相当多的代码用于提取和构建项目数据,所以让它成为它自己的函数,Project -

function Project(fromApi) {
  // add date logic, or more
  return {
    mintDate: fromApi.mintDate,
    name: fromApi.name,
    stock: fromApi.supply,
    links: [
      fromApi.discord_url,
      fromApi.magic_eden_url,
      fromApi.twitter_url
    ],
    price: fromApi.price
  }
}
read(i).then(r => {
    const mintData = []
    for (const v of r) {
      mintData.push(Project(v)) // ✅
    }
    return mintData
  }).then(...)

这与 -

相同
read(i)
  .then(r => r.map(Project)) // ✨ no for loop needed!
  .then(...)

写入

让我们重新检查您的代码并查看我们的进度 -

function rawData(socket){
  // var mintDataArray = [] // ✅ remove external state
  return new Promise(function(){
    for (let i = 1; i < 13; i+= 1) {
      read(i)
        .then(r => r.map(Project)) // ✨
        .then(function(socket) {
          // ⚠️ remember, "socket" gets the resolved promise
          // since `read` resolves an array of Projects, we should rename it
        })
    }
    // ⚠️ we'll come back to this
    // socket.emit('twitterFollowers',mintDataArray)
  })
}

我们继续 .then(function(socket){ ... }) 处理程序。这里有大量代码用于创建路径、创建目录以及将 JSON 写入文件。让我们让它成为它自己的函数 write -

import { stat, mkdir, writeFile } from "fs/promises"
import { join, resolve, dirname } from "path" //  unrelated to Promise "resolve"

function write(project, dir) {
  const dest = resolve(dir, join(project.mintDate, project.name)) // ✅ sanitary path handling
  return stat(dirname(dest))
    .catch(_ => mkdir(dirname(dest), {recursive: true})) // create dir if not exists
    .then(_ => writeFile(dest, JSON.stringify(project))) // write project JSON data
    .then(_ => project)                                  // return project
}

我们的 rawData 函数清理得很好,但是我们仍然有一个突出的问题 运行 两个独立循环中的异步操作 -

function rawData(socket){
  return new Promise(function(){
    for (let i = 1; i < 13; i+= 1) {
      read(i) //❓ how to resolve promise for each read?
        .then(r => r.map(Project))
        .then(projects => { // ✅ "projects", not "socket"
          for (const p of projects) {
            write(p, "./UpcomingCollections") //❓ how to resole promise for each write?
          }
          // ❓ what should we return?
        })
    }
    // socket.emit('twitterFollowers',mintDataArray)
  })
}

循环承诺

Promise 还有一个我们必须熟悉的功能。 Promise.all 采用一系列承诺,仅当 所有 承诺已解决时才解决 -

function myfunc(x) {
  return Promise.resolve(x * 100)
}

Promise.all([myfunc(1), myfunc(2), myfunc(3)]).then(console.log)
// Promise{ [ 100, 200, 300 ] }

Promise.all([1,2,3].map(n => myfunc(n))).then(console.log)
// Promise{ [ 100, 200, 300 ] }

我们可以使用Promise.all来清理rawData中的两个循环。看看那个,我们可以将数据直接排序到 socket -

function rawData(socket){
  return Promise.all(
    [1,2,3,4,5,6,7,8,9,10,11,12].map(i => 
      read(i).then(r => r.map(Project))
    )
  )
  .then(projects => {
    Promise.all(projects.map(p => write(p, "./UpcomingCollections"))) // ✨
  })
  .then(projects => socket.emit("twitterFollowers", projects)) // ✨ 
}

现在一起

我们可以看到,通过以一种有效的、惯用的方式使用 Promise-based 代码,很多痛点都被消除了。到目前为止,我们还没有解决错误处理的问题,但现在没什么可说的了。因为我们正确使用了 Promises,所以任何错误都会冒出来,调用者可以 .catch 它们并做出适当的响应 -

import { stat, mkdir, writeFile } from "fs/promises"
import { join, resolve, dirname } from "path"

function Project(fromApi) {
  return {
    mintDate: fromApi.mintDate,
    name: fromApi.name,
    stock: fromApi.supply,
    links: [
      fromApi.discord_url,
      fromApi.magic_eden_url,
      fromApi.twitter_url
    ],
    price: fromApi.price
  }
}

function write(project, dir) {
  const dest = resolve(dir, join(project.mintDate, project.name))
  return stat(dirname(dest))
    .catch(_ => mkdir(dirname(dest), {recursive: true}))
    .then(_ => writeFile(dest, JSON.stringify(project)))
    .then(_ => project)
}

function rawData(socket){
  return Promise.all(
    [1,2,3,4,5,6,7,8,9,10,11,12].map(i => 
      read(i).then(r => r.map(Project))
    )
  )
  .then(projects => {
    Promise.all(projects.map(p => write(p, "./UpcomingCollections")))
  })
  .then(projects => socket.emit("twitterFollowers", projects))
}

异步..等待

现代 JavaScript 提供 async..await 语法,使我们能够模糊同步代码和异步代码之间的界限。这使我们能够删除许多 .then 调用,扁平化我们的代码,减少认知负担,并在同一范围内共享异步值 -

async function rawData(socket) {
  const r = await Promise.all([1,2,3,4,5,6,7,8,9,10,11,12].map(read))
  const projects = await Promise.all(
    r.flatMap(Project).map(p => write(p, "./UpcomingCollections"))
  )
  return socket.emit("twitterfollowers", projects)
}

该程序的一些复杂性源于嵌套数据的使用。 Promise.all 是高效的并且并行运行 promises,但是将数据嵌套在数组中会使它更难处理。为了表明 async..await 确实模糊了同步和异步之间的界限,我们将带回两个 for..of 循环并在循环中调用 await。这导致 序列 订单处理,但可读性非常好。也许您的 use-case 要求不高,所以这种风格完全足够 -

async function rawData(socket) {
  for (const i of [1,2,3,4,5,6,7,8,9,10,11,12]) {
    const result = await read(i)
    for (const r of result) {
      const project = Project(r)
      await write(project, "./UpcomingCollections")
      socket.emit("twitterFollowers", project)
    }
  }
}

木兰的回答是正确的,我只需要将我另一个函数中的for循环更改为.map。在那之后,一切都很完美。我必须对 rawData 函数做一些修改,如下所示。

function Project(fromApi) {
  return {
    mintDate: moment.utc(fromApi.mintDate).format("MMMM Do"),
    name: fromApi.name,
    imageLink:fromApi.project_image_link,
    stock: fromApi.supply,
    links: [
      fromApi.discord_url,
      fromApi.magic_eden_url,
      fromApi.twitter_url
    ],
    price: fromApi.price
  }
}

function write(project, dir) {
  const dest = resolve(dir, join(project.mintDate, project.name))
  console.log(dest)
  return stat(dirname(dest))
    .catch(_ => mkdir(dirname(dest), {recursive: true}))
    .then(_ => writeFile(dest, JSON.stringify(project)))
    .then(_ => project)
}

function rawData(socket){
  return new Promise(function(resolve,reject){
    [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17].map(i => 
      read(i).then(function(r){
      var data = []
      for(i in r){
        if(r[i].__typename == "Nft"){
          data.push(r[i])
        }
      }
      data.map(item => {
        var project = Project(item)
        write(project,"./UpcomingCollections")
      })

      })
    ) 
  })
}