直接告诉你结论:用Promise封装微信小程序的异步API,能让你的代码从"金字塔"嵌套变成清晰易读的"流水线",但如果你没理解async/await的细节,可能会掉进"Promise坑"——比如我团队的新人上周就因为忘了await,对着一个永远处于pending状态的Promise调试了整整三小时。

——

一、为什么你的小程序代码还在"回调地狱"里打转?

先看一段熟悉的代码片段,这是小程序开发最常见的场景——用户登录后获取信息,然后请求服务器:
wx.login({
success: loginRes => {
wx.getUserInfo({
success: userRes => {
wx.request({
url: 'https://api.example.com/auth',
method: 'POST',
data: { code: loginRes.code, userInfo: userRes.userInfo },
success: apiRes => {
// 最终拿到数据,但这里已经嵌套了3层!
console.log(apiRes.data);
},
fail: err => {
console.error('请求失败', err);
}
});
},
fail: err => {
console.error('获取用户信息失败', err);
}
});
},
fail: err => {
console.error('登录失败', err);
}
});

这种代码就像俄罗斯套娃,每增加一个依赖前一步结果的异步操作,就得多一层嵌套——不仅难以阅读和维护,错误处理也分散在各个角落。

更糟的是,当多个异步操作需要顺序执行时,代码向右倾斜的程度会越来越严重,形成所谓的"回调地狱"(Callback Hell)。Promise的出现,正是为了解决这个问题。

——

二、Promise化:小程序异步代码的"拯救方案"

Promise是一种异步编程的解决方案,它可以将异步操作以同步操作的流程表达出来,避免层层嵌套。

2.1 如何将小程序API包装成Promise?

小程序官方API大多采用回调函数方式,但我们可以用简单的包装函数将其Promise化:
// util.js - 定义Promise化工具函数
function promisify(fn) {
return function(obj = {}) {
return new Promise((resolve, reject) => {
obj.success = function(res) { resolve(res); };
obj.fail = function(res) { reject(res); };
fn(obj);
});
};
}

// 将常用API Promise化
const wxPromisify = {
login: promisify(wx.login),
request: promisify(wx.request),
getUserInfo: promisify(wx.getUserInfo),
getSystemInfo: promisify(wx.getSystemInfo)
};

module.exports = wxPromisify;

注意:2018年后的微信小程序已原生支持ES6 Promise,无需引入第三方库。

2.2 使用Promise重构后的代码是什么样的?

用Promise重写前面的嵌套代码,效果立竿见影:
import { login, getUserInfo, request } from './utils/wxPromisify';

login()
.then(loginRes => {
return getUserInfo();
})
.then(userRes => {
return request({
url: 'https://api.example.com/auth',
method: 'POST',
data: { code: loginRes.code, userInfo: userRes.userInfo }
});
})
.then(apiRes => {
console.log(apiRes.data); // 清晰易懂的链式调用
})
.catch(err => {
console.error('操作失败', err); // 统一错误处理
});

看出差别了吗?代码从"金字塔"变成了"流水线",可读性和维护性大幅提升。

——

三、进阶技巧:async/await让异步代码更像同步代码

虽然Promise解决了回调地狱,但链式调用的then()方法仍然不够直观。ES2017引入的async/await可以让我们以近乎同步的方式编写异步代码。

3.1 async/await的基本用法

async function initUserData() {
try {
const loginRes = await login();
const userRes = await getUserInfo();
const apiRes = await request({
url: 'https://api.example.com/auth',
method: 'POST',
data: { code: loginRes.code, userInfo: userRes.userInfo }
});

console.log(apiRes.data);
return apiRes.data;
} catch (err) {
console.error('初始化用户数据失败', err);
throw err;
}
}

// 调用async函数
initUserData().then(data => {
console.log('用户数据初始化完成', data);
});

关键点:async函数总是返回一个Promise,即使你在函数中返回的是具体值,它也会被自动包装成Promise。

3.2 新手常踩的坑:忘了await!

我团队新人上周写的代码:
// 错误示例:忘了await!
const fetchData = async () => {
const result = fetchTypelist(); // 这里应该加await!
console.log(result); // 输出:Promise {<pending>},而不是实际数据
};

// 正确写法
const fetchDataCorrect = async () => {
const result = await fetchTypelist(); // 加上await等待异步操作完成
console.log(result); // 正常输出数据
};

经验:在async函数中调用其他async函数时,99%的情况都需要加await,除非你确实想获得Promise对象而不是具体值。

——

四、实战案例:封装小程序网络请求模块

在实际项目中,我们通常会封装一个完整的请求库,结合Promise和async/await使用:
// request.js - 封装统一的请求方法
const request = (url, method = 'GET', data = {}, header = {}) => {
return new Promise((resolve, reject) => {
wx.request({
url: `${API_BASE}${url}`,
method,
data,
header: {
'content-type': 'application/json',
...header
},
success: (res) => {
if (res.statusCode === 200) {
resolve(res.data);
} else {
reject(new Error(`请求失败,状态码:${res.statusCode}`));
}
},
fail: reject
});
});
};

// API模块
export const api = {
// 获取行业类型列表
getIndustryList: () => request('/industryType/page', 'GET', { pageNo: 1, pageSize: 10 }),

// 提交用户数据
submitUserData: (userData) => request('/user/submit', 'POST', userData)
};

// 在页面中使用
Page({
async onLoad() {
try {
const industryList = await api.getIndustryList();
this.setData({ industryList });
} catch (error) {
wx.showToast({ title: '数据加载失败', icon: 'none' });
}
}
});

这种封装方式让业务代码极其简洁,同时统一处理了错误和加载状态。

——

五、Promise使用的注意事项与最佳实践

1. 一定要添加catch处理
即使使用async/await,也不要忽略catch块,否则未处理的Promise拒绝可能导致难以调试的问题。

2. 合理使用Promise.all处理并行异步操作
当多个异步操作之间没有依赖关系时,可以用Promise.all同时执行:
async function initData() {
try {
const [userInfo, systemInfo] = await Promise.all([
getUserInfo(),
getSystemInfo()
]);
// 两个请求都完成后执行
console.log(userInfo, systemInfo);
} catch (err) {
// 任何一个失败都会进入catch
console.error('初始化失败', err);
}
}

3. 在需要中断的地方使用Promise.reject
可以在then链中通过返回Promise.reject()提前终止流程。

最关键的一步:封装好Promise函数后,在业务代码中坚持使用async/await而非传统的then()链,这样代码意图会更清晰。