从 promise 到底要不要加 return 开始

7,063 阅读4分钟

最近写代码,发现涉及到promise的地方,有时候写了return,有时候没写return,promise了解的还不够清楚,现整理一下。

promise.then()

const fnc = () => {
  return new Promise((resolve) => {
    resolve("返回值");
  });
};
const cb = () => {
  return "新的值";
};

fnc()
  .then(() => {
    return cb();
  })
  .then((res) => console.log(res)); // 新的值

fnc()
  .then(() => {
    cb();
  })
  .then((res) => console.log(res)); // undefined

fnc()
  .then(cb())
  .then((res) => console.log(res)); // 返回值

promise.then(onFulfilled, onRejected)接收两个参数,一个是状态变为resolve后的回调函数,一个是状态变为reject后的回调函数(此处只讨论onFulfilled)

  • 如果onFulfilled是一个函数,且有返回值,则返回值可以继续传递给后续的then
  • 如果onFulfilled是一个函数,但没有返回值(相当于return undefined),则后续then中的入参为undefined
  • 如果onFulfilled不是函数,则忽略当前then,将参数直接传递给后续的then

如果then中又有其他的promise呢?

fnc()
  .then((res) => {
    new Promise((resolve) => {
      setTimeout(resolve, 5000, 123);
    });
  })
  .then((res) => {
    console.log(res);
  }); // undefined, 5s后定时器结束,即then没有等待前面的Promise
  
fnc()
  .then((res) => {
  // 这里有return
   	return new Promise((resolve) => {
      setTimeout(resolve, 5000, 123);
    });
  })
  .then((res) => {
    console.log(res);
  }); // 5s后输出123

注意两种方法的区别,如果没有return,这里的promise会成为一个副作用,然后把undefined传递给后续的then, then并不会等待这个promise
只有当onFulfilled的返回值是一个Promise时,后续then才会等该promise状态变化后才执行
所以写then时保持有return或者throw的好习惯

  • return 另一个promise
  • return 一个同步的值或者undefined
  • throw一个异常

书写风格

请求接口a,a接口返回结果后请求接口b,b接口返回结果后请求接口c

const interfaceA = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("接口a返回的数据");
    }, 1000);
  });
};

const interfaceB = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("接口b返回的数据");
    }, 1000);
  });
};

const interfaceC = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("接口c返回的数据");
    }, 1000);
  });
};

// 不好的书写风格
interfaceA().then((res) => {
  console.log(res);
  interfaceB().then((res) => {
    console.log(res);
    interfaceC().then((res) => {
      console.log(res);
    });
  });
});
// 使用return正确的书写风格
interfaceA()
  .then((res) => {
    console.log(res);
    return interfaceB();
  })
  .then((res) => {
    console.log(res);
    return interfaceC();
  })
  .then((res) => {
    console.log(res);
  });

注意事项

  • promise一定要有resolve或者reject

一个场景

  1. 转换接口返回格式为需要的格式
    我们基于antd封装了一个带分页的请求后端接口的MyTable组件
// 使用时 index.js
import React from 'react'
import { MyTable } from './table'

// 模拟后端接口,总共有50条数据,每次返回当前页中的10条
const getPageData = page => {
  return new Promise((reslove) => {
        let source = []
        const cur = (page - 1) * 10
        for (let i = cur; i < cur + 10; i++) {
          source.push({
            key: '1',
            name: '小瓦',
            age: i,
            address: '西湖区湖底公园1号',
          })
        }
        setTimeout(() => {
          const res = { total: 50, data: source }
          reslove(res)
        }, 1000)
      })
}

export const Demo = () => {
  const getList = (page) => {
    return getPageData(page) // 一定要加return 
  }
  return <MyTable query={getList} />
}

// table.js
import React, { useEffect, useState } from 'react'
import { Table } from 'antd'

const columns = [
  {
    title: '姓名',
    dataIndex: 'name',
    key: 'name',
  },
  {
    title: '年龄',
    dataIndex: 'age',
    key: 'age',
  },
  {
    title: '住址',
    dataIndex: 'address',
    key: 'address',
  },
]
export const MyTable = (props) => {
  const { query } = props
  const [total, setTotal] = useState(0)
  const [data, setData] = useState([])
  const [page, setPage] = useState(1) // 当前页码

  useEffect(() => {
    query(page)
      .then((res) => {
        const { total, data } = res // 这里得确保,query返回的res是{total, data}格式
        setData(data)
        setTotal(total)
      })
      .catch(() => {})
  }, [page, query]) // page改变时请求接口

  return (
    <Table
      dataSource={data}
      columns={columns}
      pagination={{
        total: total,
        defaultCurrent: 1,
        current: page,
        onChange: (page, pageSize) => setPage(page),
      }}
    />
  )
}

问题来了,如果有个后端接口放回的数据格式并不是{total, data}这样的,比如接口返回了{all, list},那么这个组件就不能复用了,当然可以直接跟后端沟通,要求返回字段格式,不过我们也可以转一下接口格式。

// 之前
export const Demo = () => {
  const getList = (page) => {
    return getPageData(page) // getPageDate返回{all, list}
  }
  return <MyTable query={getList} />
}
// 转换
export const Demo = () => {
  const getList = (page) => {
    return getPageData(page).then(res=>{
    	return { 
            // 一定要加return
            total: res.all
            data: res.list
        }
    })
  } 
  return <MyTable query={getList} />
}

async await

async和await可以更加语义化的解决回调地域问题,前面的“请求接口a,a接口返回结果后请求接口b,b接口返回结果后请求接口c”的场景,可以用 async + await 改写。

// 使用promise
interfaceA()
  .then((res) => {
    console.log(res);
    return interfaceB();
  })
  .then((res) => {
    console.log(res);
    return interfaceC();
  })
  .then((res) => {
    console.log(res);
  });
 
 // 使用async + await,更加语义化
 const fn = async () => {
    const res1 = await interfaceA()
    const res2 = await interfaceB() 
    const res3 = await interfaceC() 
 }

await只是一种语法糖,只有当后面为promise时才会等待,原理同promise.then

// then中没有return
const fn = () => {
  return new Promise((reslove) =>
    setTimeout(() => {
      console.log("第一个promise");
      reslove("返回值");
    }, 1000)
  ).then((res) => {
    new Promise((reslove) => {
      setTimeout(() => {
        console.log("第二个promise");
        reslove(res);
      }, 1000);
    });
  });
};

const call = async () => {
  const res = await fn();
  console.log("async函数");
  console.log("resres", res);
};

call(); // 1s后输出'第一个promise\async函数\resres,undefined',再1s后输出'第二个promise'

// then中有return
const fn = () => {
  return new Promise((reslove) =>
    setTimeout(() => {
      console.log("第一个promise");
      reslove("返回值");
    }, 1000)
  ).then((res) => {
    return new Promise((reslove) => {
      setTimeout(() => {
        console.log("第二个promise");
        reslove(res);
      }, 1000);
    });
  });
};

const call = async () => {
  const res = await fn();
  console.log("async函数");
  console.log("resres", res);
};

call(); // 1s后输出'第一个promise',再1s后输出'第二个promise\async函数\resres,返回值'