【问题标题】:Return data from node promises从节点承诺返回数据
【发布时间】:2017-06-28 11:50:59
【问题描述】:

我目前正在尝试使用 Promise 从 Spotify API 获取数据,昨天我在另一个关于同一主题的问题上获得了巨大帮助:“Loop the object returned from node promise and feed to the next .then”。

我首先从我的播放列表中获取曲目,然后调用另一个 api 来获取艺术家。最后,我调用了另一个获取艺术家图像的 api。 现在我的问题是:如何返回从我的承诺中获得的数据?

这是我获取播放列表网址的函数:

function getPlaylists(access_token) {

    var options = {
      url: 'https://api.spotify.com/v1/me/playlists',
      headers: { 'Authorization': 'Bearer ' + access_token },
      json: true
    };

    return new Promise(function(resolve, reject) {

      request.get(options, function(error, response, body) {
        var playlists = body.items;
        var playlistArray = [];
        playlists.forEach(function(playlist) {
          var name = playlist.name;
          var url = playlist.tracks.href;

          playlistArray.push(url);
        });

        if(!error) {
          resolve(playlistArray);
        } else {
          reject(error);
        }

    });

  });
}

这个得到了艺术家:

function getArtists(url,access_token) {

  var params = {
    url: url,
    headers: { 'Authorization': 'Bearer ' + access_token },
    json: true
  };

  return new Promise(function(resolve, reject) {

    request.get(params, function(error, response, body) {

        var tracks = body.items;
        var artistArray = [];
        tracks.forEach(function(artists) {
          let allArtists = artists.track.artists;
          allArtists.forEach(function(artist) {
              artistArray.push(artist);
          });
        })

        if(!error) {
          resolve(artistArray);
        } else {
          reject(error);
        }

    });

  })

}

这个得到了艺术家的形象:

function getArtistImages(artistId) {
    var options = {
      url: 'https://api.spotify.com/v1/artists/' + artistId,
      json: true
    };

    return new Promise(function(resolve, reject) {
      request.get(options, function(error, response, body) {
          if(error != null) {
              reject(error);
          } else {
              resolve(body);
          }
      });
    })

}

编辑编辑 我调用这些函数的方式是这样的:

getPlaylists(access_token)
      .then(playlists => Promise.all(playlists.map(playlist =>
        getArtists(playlist, access_token)))
      .then(artists => {

        artists.map(artist => {
          artist.map(a => {
            console.log(a);
            let component = renderToString(
                <App>
                    <Table artists={a} />
                </App>
            );

            res.send(
              component
            )
          })


        })

      }));

它只返回第一个结果 - 显然是因为它只在“res.send()”之前循环一次 forEach 循环,所以在渲染视图之前如何确保它循环通过所有艺术家?我相信我必须再做一次 Promise.all(),但我不确定在哪里 - 有人知道吗?

我很感激 :)

【问题讨论】:

  • 我仍然没有弄清楚你的问题可能是什么解决方案,但我现在可以说的是你没有在 artist.map() 函数中返回任何内容。另外,我没有看到array.push(obj[key]) 中的key 是什么。
  • 哦,是的,我忘了从测试中删除变量键。我相信我必须在某个地方再做一次 Promise.all 才能让我的艺术家图像正确?
  • 你能解释一下数据和它的结构吗?因为通过你的代码告诉它很混乱。例如,getArtists 似乎返回一个 (Promise of) 艺术家数组,而 getArtistImages 返回一个 img?持有一个 (可选) 图像数组,其中包含实际图像
  • 没有实际数据很难测试,但似乎getArtistsImages() 只返回元素(或者你正在处理这样的事情)。另外,我认为第一个Promise.all() 包装得太多了。我认为您需要另一个 Promise.all() 包围 artists.map() 来等待每个艺术家都被填充。
  • 嗨,请检查编辑 - 在 Promise.all 中包装了 Artist.map

标签: javascript node.js


【解决方案1】:

它的旧帖子,但我认为它可能对某人有所帮助。

我编写了一个插件来执行基于支持并发的承诺的 foreach。我看到您不需要并发,这会使应用其他解决方案变得更简单。

我使用我的插件通过您的代码编写了一个代码。有效!

'use strict';

var request = require('request')
var promiseForeach = require('promise-foreach')

function getPlaylists(access_token) {

    var options = {
        url: 'https://api.spotify.com/v1/me/playlists',
        headers: { 'Authorization': 'Bearer ' + access_token },
        json: true
    };

    return new Promise(function (resolve, reject) {

        request.get(options, function (error, response, body) {
            var playlists = body.items;
            var playlistArray = [];
            playlists.forEach(function (playlist) {
                var name = playlist.name;
                var url = playlist.tracks.href;
                playlistArray.push(url);
            });
            if (!error) {
                resolve(playlistArray);
            } else {
                reject(error);
            }
        });

    });
}

function getArtists(url, access_token) {

    var params = {
        url: url,
        headers: { 'Authorization': 'Bearer ' + access_token },
        json: true
    };

    return new Promise(function (resolve, reject) {

        request.get(params, function (error, response, body) {

            var tracks = body.items;
            var artistArray = [];
            tracks.forEach(function (artists) {
                let allArtists = artists.track.artists;
                allArtists.forEach(function (artist) {
                    artistArray.push(artist);
                });
            })

            if (!error) {

                promiseForeach.each(artistArray,
                    [function (artist) {
                        return getArtistImages(artist.id)
                    }],
                    function (arrayOfResultOfTask, currentList) {
                        return {
                            artistId: currentList.id,
                            artistName: currentList.name,
                            artistImages: arrayOfResultOfTask[0].images
                        }
                    },
                    function (err, newList) {
                        if (err) {
                            console.error(err)
                            return;
                        }
                        resolve(newList)
                    })

            } else {
                reject(error);
            }

        });

    })

}

function getArtistImages(artistId) {
    var options = {
        url: 'https://api.spotify.com/v1/artists/' + artistId,
        headers: { 'Authorization': 'Bearer ' + access_token },
        json: true
    };

    return new Promise(function (resolve, reject) {
        request.get(options, function (error, response, body) {
            if (error != null) {
                reject(error);
            } else {
                resolve(body);
            }
        });
    })

}

var access_token = 'YOUR-TOKEN';


getPlaylists(access_token)
    .then(playlists => {

        promiseForeach.each(playlists,
            [function (playlist) {
                return getArtists(playlist, access_token)
            }],
            function (arrayOfResultOfTask, currentList) {
                return {
                    playlistURL: currentList,
                    artist: arrayOfResultOfTask[0]
                }
                //return renderToString(
                //    <App>
                //        <Table artists={render} />
                //    </App>
                //);
            },
            function (err, newList) {
                if (err) {
                    console.error(err)
                    return;
                }
                res.send(newList)
            })

    });

插件:https://www.npmjs.com/package/promise-foreach

希望对大家有所帮助!

【讨论】:

    【解决方案2】:

    如果我正确理解了您的问题,听起来您在如何实际使用 Promises 的结果方面陷入困境。

    异步应用程序:

    Promises 封装了一个异步结果,可以通过传递给then() 的回调获得。

    这种异步机制将在您的应用程序中级联。

    应用程序管理异步结果的方式有很多种:事件、回调、可观察对象、承诺...

    示例(使用事件):

    这是一个粗略且未经测试的示例,说明来自异步请求的数据如何被注入到您的视图中。当我的异步请求调用我的then() 回调时,我会更新我的模型,该模型会触发一个事件以重新呈现我的视图。

    这个例子完全有问题(即,如果我不想重新渲染我的整个视图怎么办?如果我的 getArtists() 返回的时间比我的视图渲染它的加载状态要快怎么办?)。但为简单起见,我们不会去那里。

    +function(){
    
    
        var _view = $('#viewport');
        var _model = {...}
        var _spotifyClient = new SpotifyClient(); // this contains method similar to those you included in your question
    
        _view.on('load', onLoad);
        _view.on('model:update', onModelUpdate);
    
        function onLoad() {
           _spotifyClient
             .getArtists()
             .then(function(result) {
               // when the getArtists() request has responded, I can update my model.
               updateModel({ isLoading: false, artists: result });
             })        
    
           // this will happen immediately after starting the "getArtists()" request.
           udpateModel({ isLoading: true });
        }
    
        function updateModel(mod) {
           for(var p in mod) {
              _model[p] = mod[p];
           }
           _view.trigger('model:update', _model);
        }        
    
        function onModelUpdate(model) {
           refreshView();
        }
    
        function refreshView() {
           var viewModel = buildTemplateModel(_model);
           renderTemplate(_view, viewModel);
        }
    
    }(); 
    

    我鼓励您研究一些视图框架,例如 angular、knout 或 react。我还鼓励你研究 Reactive Extensions,它提供了一个可观察的接口和许多关于异步流的实用程序。

    副作用说明:

    承诺的结果触发事件可以归类为“副作用”。您应该在我们的应用程序中将副作用保持在最低限度,它们实际上只属于您应用程序的控制器/主要部分。

    如果您在应用程序中使用 Promise,则可重用的库类和函数(对 Promise 进行操作)应该返回 Promise。

    【讨论】:

    • 是的,我想我还没有真正理解承诺:) 请检查新的编辑,我想我快到了
    【解决方案3】:

    您需要在所有结果的数组上调用res.send,而不是分别调用:

    getPlaylists(access_token).then(playlists =>
        Promise.all(playlists.map(playlist =>
            getArtists(playlist, access_token)
        ))
    ).then(artists => {
        res.send(artists.map(artist =>
            artist.map(a => {
                console.log(a);
                return renderToString(
                    <App>
                        <Table artists={a} />
                    </App>
                );
            })
        ))
    });
    

    您的括号也略有错误(与then 调用的缩进不匹配),但这并没有导致问题。

    【讨论】:

    • 嗨,嗯,它返回一个 html 数组 - 很奇怪。
    • 呃,这不是你想要的吗?
    • 我想要你所说的关于返回所有结果的数组。但是,这会返回整个页面的数组:像这样: "
    • @MathiasLund 我不知道 renderToString 到底是做什么的以及你的 AppTable 组件做什么,但它们负责在数组中创建单个结果。
    猜你喜欢
    • 1970-01-01
    • 2015-03-14
    • 2019-03-07
    • 2018-10-11
    • 2017-07-18
    • 2017-04-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多