返回

Promise 链接访问数据时避免意外结果的指南(Node-fetch)

javascript

如何通过 Promise 链接访问数据(Node-fetch)

作为开发人员,在处理异步操作时经常需要使用 Promise。在 Node.js 中,我们可以使用 node-fetch 库来发送网络请求并处理响应。然而,有时在处理嵌套的 Promise 链时,我们可能会遇到意外的问题。

问题

设想这样一个场景:我们要从服务器获取一个用户对象,该对象包含姓名、年龄、性别等信息。我们通过 Promise 链来实现,首先获取一个登录令牌,然后使用令牌获取用户数据。但是,当我们尝试访问用户对象时,却得到了一个意外的结果。

以下是如何重现该问题的代码片段:

const fetch = require('node-fetch');

fetch('http://34.245.86.200:9084/api/auth/login', {
    method: 'POST',
    body: JSON.stringify({
        Identifier: '17123456788',
        Password: 'bambam'
    }),
    headers: {
        'Content-type': 'application/json'
    }
}).then(response => {
    return response.json()
}).then(
    data => {
        const header = {
            'Authorization': 'Bearer ' + data.token
        }

        return fetch('http://34.245.86.200:9085/api/user', {
            method: 'GET',
            headers: header
        })
    }).then(msg => {
        console.log(msg.body)
    })

期待的结果是:

{
    "title": null,
    "firstName": "Kay",
    "lastName": "Atom",
    "primaryPhoneNumber": "+1 712-345-6788",
    "fullName": "Kay Atom",
    "sex": null,
    "email": "[email protected]",
    "image": null,
}

然而,实际的控制台输出却令人惊讶:

{
    token:
    'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJrYXlAYmFtLmNvbSIsImVtYWlsIjoia2F5QGJhbS5jb20iLCJnaXZlbl9uYW1lIjoiS2F5Iiwic2lkIjoiMzQ0MDM1ODM5NGZmNGUxNzhkMmJhNzcxNmVjYjM3YTgiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJQYXRpZW50IiwiYXV0aF90aW1lIjoiMDcvMDcvMjAyMCAxNzowOTo1MCIsImV4cCI6MTU5NDE0ODk5MCwiaXNzIjoiTGVpbGFQcm9qZWN0IiwiYXVkIjoiTGVpbGFQcm9qZWN0In0.DHuxUY77Jrsd_wBOrcZ-cmqvE8nh5I3TzpLIrRUuuhI',

    message: 'Successfully Logged In [email protected]'
}

PassThrough {
    _readableState: ReadableState {
        objectMode: false,
        highWaterMark: 16384,
        buffer: BufferList {
            head: [Object],
            tail: [Object],
            length: 1
        },
        length: 372,
        pipes: [],
        flowing: null,
        ended: false,
        endEmitted: false,
        reading: true,
        sync: false,
        needReadable: true,
        emittedReadable: false,
        readableListening: false,
        resumeScheduled: false,
        errorEmitted: false,
        emitClose: true,
        autoDestroy: true,
        destroyed: false,
        errored: false,
        closed: false,
        closeEmitted: false,
        defaultEncoding: 'utf8',
        awaitDrainWriters: null,
        multiAwaitDrain: false,
        readingMore: false,
        decoder: null,
        encoding: null,
        [Symbol(kPaused)]: null
    },
    _events: [Object: null prototype]{
        prefinish: [Function: prefinish],
        unpipe: [Function: onunpipe],
        error: [[Function: onerror], [Function(anonymous)]],
        close: [Function: bound onceWrapper]{
            listener: [Function: onclose]
        },
        finish: [Function: bound onceWrapper]{
            listener: [Function: onfinish]
        }
``
    },
    _eventsCount: 5,
    _maxListeners: undefined,
    _writableState: WritableState {
        objectMode: false,
        highWaterMark: 16384,
        finalCalled: false,
        needDrain: false,
        ending: false,
        ended: false,
        finished: false,
        destroyed: false,
        decodeStrings: true,
        defaultEncoding: 'utf8',
        length: 0,
        writing: false,
        corked: 0,
        sync: false,
        bufferProcessing: false,
        onwrite: [Function: bound onwrite],
        writecb: null,
        writelen: 0,
        afterWriteTickInfo: null,
        buffered: [],
        bufferedIndex: 0,
        allBuffers: true,
        allNoop: true,
        pendingcb: 0,
        prefinished: false,
        errorEmitted: false,
        emitClose: true,
        autoDestroy: true,
        errored: false,
        closed: false
    },
    allowHalfOpen: true,
    _transformState: {
        afterTransform: [Function: bound afterTransform],
        needTransform: true,
        transforming: false,
        writecb: null,
        writechunk: null,
        writeencoding: 'buffer'
    },
    [Symbol(kCapture)]: false

解决方法

问题的根源在于我们如何处理第二个 fetch 请求的响应。在原始代码中,我们使用 console.log(msg.body) 记录响应正文。虽然这可以显示响应的原始正文,但它不会将正文解析为 JSON 对象。

为了正确解析响应正文并访问用户对象,我们需要使用 response.json() 方法。以下是更新后的代码片段:

const fetch = require('node-fetch');

fetch('http://34.245.86.200:9084/api/auth/login', {
    method: 'POST',
    body: JSON.stringify({
        Identifier: '17123456788',
        Password: 'bambam'
    }),
    headers: {
        'Content-type': 'application/json'
    }
}).then(response => {
    return response.json()
}).then(
    data => {
        const header = {
            'Authorization': 'Bearer ' + data.token
        }

        return fetch('http://34.245.86.200:9085/api/user', {
            method: 'GET',
            headers: header
        })
    }).then(response => response.json()).then(user => {
        console.log(user)
    })

通过这种方式,我们可以正确解析响应正文并访问用户对象。

常见问题解答

1. 为什么使用 Promise 链接来访问数据?

Promise 链接是一种处理异步操作的有效方式,因为它允许我们以顺序方式执行任务,并处理每个任务的结果。

2. 如何链式 Promise?

可以通过使用 then() 方法来链式 Promise。then() 方法接受一个回调函数,该回调函数将前一个 Promise 的结果作为输入,并返回一个新的 Promise。

3. 为什么需要解析响应正文?

HTTP 响应的正文通常是未解析的文本。为了访问响应正文中的数据,我们需要将其解析为 JSON、XML 或其他格式。

4. node-fetch 库有什么好处?

node-fetch 库是一个 Node.js 库,它提供了类似于浏览器 fetch API 的功能。它易于使用,并且支持各种选项,例如超时和重试。

5. 如何避免类似的问题?

在处理异步操作时,需要注意以下事项:

  • 始终确保正确解析响应正文。
  • 处理 Promise 链时要小心,尤其是嵌套 Promise 链。
  • 使用调试工具来帮助识别和解决问题。