零、回顾
在上节课我们主要介绍了Promise,
这节课我们开始实践多个网络请求并发执行的处理策略,
并着手将原来的登录代码模块,使用Promise改写一下。
我们设想有这样一个场景,在页面启动的时候,我们需要加载多个接口,从多个接口中拉取页面初始化所需要的数据。
如果我们对这些接口的调用,采用完成一个加载一个的方法的话,就是对小程序并发优势的浪费了。
在Chrome浏览器中,对于同一域名下的资源加载量,最大并发数字是6,默认这个数字是6,其他浏览器稍有差别,但是一般都不会超过10个。
为了优化这个问题,有网站将资源分成几个域名,分别进行加载,一个域名不到10个,那多个域名加起来就大于10了。
在小程序中,并发原本是有限制的,像wx.request、wx.uploadFile、wx.downloadFile这些资源请求加起来,并发总数不能超过10个,
如果超出了,超出的请求将会被丢弃。开发者因为害怕这个链接被莫名地丢弃,所以在写代码的时候,都小心翼翼地维护并发的总数。
后来微信在2017年7月发布了基础库1.4.0版本,对网络请求做了优化,对超过并发限制的请求,做了队列处理,
超过10个数字的请求,不会再被丢弃了。对于开发者来讲我们不用管同时有几个请求,我们只需要以一种机制让网络请求并发最大化就可以了。
其他的交给小程序逻辑层去处理就可以了。
在上节课,留了一个问题,如何进行多服务器ip的连接,接下来我们在本课的源码中实践一下,下面这三个方法的具体应用。
promise.any()
promise.all()
promise.race()
注意
:
API Promise化链接:
https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/extended/utils/api-promise.html
`这个网址打开,现在是404`
异步 API 返回 Promise
基础库 2.10.2 版本起,异步 API 支持 callback & promise 两种调用方式
。当接口参数 Object 对象中不包含 success/fail/complete 时将默认返回 promise,否则仍按回调方式执行,无返回值。
注意事项
- 部分接口如
downloadFile
,request
,uploadFile
,connectSocket
,createCamera
(小游戏)本身就有返回值, 它们的 promisify 需要开发者自行封装。 - 当没有回调参数时,异步接口返回 promise。此时若函数调用失败进入 fail 逻辑, 会报错提示
Uncaught (in promise)
,开发者可通过 catch 来进行捕获。 wx.onUnhandledRejection
可以监听未处理的 Promise 拒绝事件。
代码示例
`callback 形式调用`
wx.chooseImage({
success(res) {
console.log('res:', res)
}
})
`promise 形式调用`
wx.chooseImage().then(res => console.log('res: ', res))
一、将wx.request这个接口转化为返回Promise对象的接口
在默认情况下,wx.request返回的是RequestTask对象,我们需要将它修改为Promise对象、
现在小程序确实不再是一个简单的前端框架,当我们需要将接口实现Promise化的时候,
微信团队提供了这样一个模块,miniprogram-api-promise,它可以扩展微信小程序API,使API支持promise编程。
API Promise化链接:
https://developers.weixin.qq.com/miniprogram/dev/platform-capabilities/extended/utils/api-promise.html
这个网址打开,现在是404
安装:
npm install --save miniprogram-api-promise
在使用npm指令安装以后,我们进行初始化。
import { promisifyAll, promisify } from 'miniprogram-api-promise';
const wxp = {}
promisifyAll(wx, wxp)
// 示例
wxp.getSystemInfo().then(console.log)
......
App({
wxp:wxp
})
上面我们现在看到的这个,就是一个初始化的代码,从第一行到第三行是它的初始化代码。
在初始化以后,wxp将可以替代wx,提供接口调用服务。
在小程序中,并没有像小游戏那样有一个GameGlobal这样的一个对象。
wx它是一个全局对象,在任何页面中都可以直接使用,
为了方便使用,刚才我们创建的wxp,我们可以将wxp挂载于App对象上,
然后在调用的时候我们可以通过getApp取到App对象,
然后再间接地取到wxp对象,这样就可以使用,就可以方便在每个页面上去使用它了。
一、any
any方法目前是处于试验阶段,处于草案提案当中,并未被所有的宿主环境所支持。
在小程序的Promise对象上,并没有any这个方法,我们目前只能换一种方式,去间接地实现它
安装
工具–构建npm
app.js
index.wxml
<view class="page-section">
<text class="page-section__title">3.4 any & all & race</text>
<view class="btn-area">
<button bindtap="any" type="primary">请求1</button>
</view>
</view>
index.js
any(e){
const app = getApp();
// 1.先创建3个promise子实例
let promise1 = app.wxp.request({url: "http://localhost:30001"})
.catch(err => { console.log("promise1")});
let promise2 = app.wxp.request({ url: "http://localhost:30002/hi" })
.catch(err => { console.log("promise2") });
let promise3 = app.wxp.request({ url: "http://localhost:30001/user/home" })
.catch(err => { console.log("promise3") });
// 2.在通过Promise.any集合成为一个总的实例,然后进行执行
let promise = Promise.any([promise1, promise2, promise3]).then(res=>{
console.log("res1",res);
},err=>{
console.log("err1", err);
})
// 3.我们期许的结果是,这三个接口任何一个接口调用成功,会走到res里面,
// 所有的调用失败才会走到err里面
},
VM1031:1 thirdScriptError
Promise.any is not a function; [Component] Event Handler Error @ pages/api/index#bound any
TypeError: Promise.any is not a function
at Se.any (http://127.0.0.1:16793/appservice/pages/api/index.js:30:27)
at Object.r.safeCallback (http://127.0.0.1:16793/appservice/__dev__/WAService.js:2:1555740)
at http://127.0.0.1:16793/appservice/__dev__/WAService.js:2:1675073
at c (http://127.0.0.1:16793/appservice/__dev__/WAService.js:2:1683089)
at http://127.0.0.1:16793/appservice/__dev__/WAService.js:2:1674998
at r (http://127.0.0.1:16793/appservice/__dev__/WAService.js:2:1627525)
at http://127.0.0.1:16793/appservice/__dev__/WAService.js:2:1627647
at http://127.0.0.1:16793/appservice/__dev__/WAService.js:2:805361
at n (http://127.0.0.1:16793/appservice/__dev__/asdebug.js:1:28029)
at e.exports.<anonymous> (http://127.0.0.1:16793/appservice/__dev__/asdebug.js:1:28398)
Promise.any is not a function
any这个方法目前在草案这个提案当中,它不是一个正式发布的方法。
现在我们如果要实现这样的一个功能,我们怎么去做?
如果我们还想用any这个方法的话,我们可以自己去实现,
因为javascript它作为一种动态语言,它就有这种好处,我们可以没有的方法,我们可以自己动手去实现。
/**
* @param {Array<Promise>} promises
* @returns {Promise}
*/
//我们直接把这个方法写在Promise类对象上,我们就可以直接使用 Promise.any
//接受一个数组作为它的参数,
Promise.any = (arr) => {
// 一共有多少个子实例
let numTotal = arr.length;
// 状态已经改变了多少个,作为一种计数
let numSettled = 0;
// 它是否已经是接受状态了
let resolved = false;
// 然后里面也是返回promise对象,同时它接受的也是一个匿名函数
return new Promise((reslolve, reject) => {
// for循环
for (let j = 0; j < numTotal;j++){
// 每个promise实例,我们可以先取出来
let p = arr[j];
// 然后在作为一个监听
p.then(res=>{
// 如果有一个成功,就代表本身成功
// 我们把resolved变量设置为true
resolved = true;
//同时调它本身的回调函数
reslolve();
},err=>{
console.log("any err",err)
}).finally(res=>{
// 首先是计量,numSettled 它有多少个状态改变了,递增
numSettled++
// 递增之后,我们要检查一下,当前我们改变状态的数字,是不是已经达到了总数,
// 如果达到了总数,并且它目前还不出于resolved解决状态
// 这个就相当于所有的执行失败了,我们再调用reject方法,
if (numSettled >= numTotal && !resolved) reject("all failed");
})
}
});
}
3个接口都是fail的前提下,
刚刚出现的问题是,我们在外面的子实例上面然后加了catch,加了catch以后,没有进去,注释catch,就可以进入到err1中。
所以说我们目前对于any的实现,它其实说并不是特别完整,它只能说在一定条件之下,可以满足我们的需求
二、all
all这种方式,是所有的子实例的成功才算成功,有一失败便是失败。
index.wxml
<view class="page-section">
<text class="page-section__title">3.4 any & all & race</text>
<view class="btn-area">
<button bindtap="any" type="primary">any</button>
<button bindtap="all" type="primary">all</button>
</view>
</view>
index.js
`3.4 all all本身它是属于promise对象的一个方法,不需要我们去模拟实现了`
all(e) {
const app = getApp();
// 1.先创建3个promise子实例
let promise1 = app.wxp.request({ url: "http://localhost:30001" })
// .catch(err => { console.log("promise1")});
let promise2 = app.wxp.request({ url: "http://localhost:30002/hi" })
// .catch(err => { console.log("promise2") });
let promise3 = app.wxp.request({ url: "http://localhost:30001/user/home" })
// .catch(err => { console.log("promise3") });
// 2.在通过Promise.any集合成为一个总的实例,然后进行执行
let promise = Promise.all([promise1, promise2, promise3]).then(res => {
console.log("res1", res);
}, err => {
console.log("err1", err);
})
},
`3.4 all all本身它是属于promise对象的一个方法,不需要我们去模拟实现了`
all(e) {
const app = getApp();
// 1.先创建3个promise子实例
let promise1 = app.wxp.request({ url: "http://localhost:3000" })
// .catch(err => { console.log("promise1")});
let promise2 = app.wxp.request({ url: "http://localhost:3000/hi" })
// .catch(err => { console.log("promise2") });
let promise3 = app.wxp.request({ url: "http://localhost:3000/user/home" })
// .catch(err => { console.log("promise3") });
// 2.在通过Promise.any集合成为一个总的实例,然后进行执行
let promise = Promise.all([promise1, promise2, promise3]).then(res => {
console.log("res1", res);
}, err => {
console.log("err1", err);
})
},
三、race
有一个成功就算成功,有一个失败也算失败,就看哪个子实例执行的快。
index.wxml
<view class="page-section">
<text class="page-section__title">3.4 any & all & race</text>
<view class="btn-area">
<button bindtap="any" type="primary">any</button>
<button bindtap="all" type="primary">all</button>
<button bindtap="race" type="primary">race</button>
</view>
</view>
`3.4 race 是赛跑机制,只要这三个里面有一个有结果了,然后就马上返回一个结果。`
race(e) {
const app = getApp();
// 1.先创建3个promise子实例
let promise1 = app.wxp.request({ url: "http://localhost:30001" })
// .catch(err => { console.log("promise1")});
let promise2 = app.wxp.request({ url: "http://localhost:30001/hi" })
// .catch(err => { console.log("promise2") });
let promise3 = app.wxp.request({ url: "http://localhost:3000/user/home" })
// .catch(err => { console.log("promise3") });
let promise = Promise.race([promise1, promise2, promise3]).then(res => {
console.log("res1", res);
}, err => {
console.log("err1", err);
})
},
四、总结
这节课我们主要实践练习了Promise的三个方法,any、all、race,
其中any还处于草案阶段,不过对Javascript这样的动态语言,对于缺失的方法我们可以自行补全。
下节课我们尝试将登录模块,使用Promise变成的方式进行改写。