美文网首页H5微信小程序小程序
微信小程序中使用Promise进行异步流程处理

微信小程序中使用Promise进行异步流程处理

作者: 一斤代码 | 来源:发表于2016-11-02 15:22 被阅读23334次

我们知道,JavaScript是单进程执行的,同步操作会对程序的执行进行阻塞处理。比如在浏览器页面程序中,如果一段同步的代码需要执行很长时间(比如一个很大的循环操作),则页面会产生卡死的现象。

所以,在JavaScript中,提供了一些异步特性,为程序提供了性能和体验上的益处,比如可以将代码放到setTimeout()中执行;或者在网页中,我们使用Ajax的方式向服务器端做异步数据请求。这些异步的代码不会阻塞当前的界面主进程,界面还是可以灵活的进行操作,等到异步代码执行完成,再做相应的处理。

一段典型的异步代码类似这样:

function asyncFunc(callback) {
  setTimeout(function () {
    //在这里写你的逻辑代码
    //...

    //逻辑代码结束,执行一个回调函数
    callback();
  }, 5000);
}

或者:

function getAccountInfo(callback, errorCallback) {
  wx.request({
    url: '/accounts/12345',
    success: function (res) {
      //...
      callback(data);
    },
    fail: function (res) {
      //...
      errorCallback(data);
    }
  });
}

然后我们这样调用:

asyncFunc(function () {
  console.log("asyncFunc() run complete");
});

getAccountInfo(function (data) {
  console.log("get account info successfully:", data);
}, function () {
  console.error("get account info failed");
});

这是一种使用了回调函数来控制代码执行流程的方式。这样看起来没问题,也挺容易理解。

但是,如果我们一段代码中,异步操作太多,又要保证这些异步操作是有顺序的执行,那我们的代码就看起来非常糟糕,就像这样:

asyncFunc1(function(){
  //...
  asyncFunc2(function(){
    //...
    asyncFunc3(function(){
      //...
      asyncFunc4(function(){
        //...
        asyncFunc5(function(){
           //...
        });
      });
    });
  });
});

这样的代码可读性和可维护性可想而知了。还有,回调函数真正的问题在于:

它剥夺了我们使用 return 和 throw 这些关键字的能力。

那有什么办法来改善这个问题呢?答案是肯定的,Promise这种概念的产生,很好地解决了这一切。关于什么是Promise,一搜一大把介绍,我这里就不复制粘贴了,我主要是讲一下我们怎么用它来解决我们的问题。

我们来看一下,上面的例子如果使用Promise,它会是什么样子?我们先将这些函数变成Promise的方式:

function asyncFunc1(){
  return new Promise(function (resolve, reject) {
    //...
  })
}

// asyncFunc2,3,4,5也实现成跟asyncFunc1一样的方式...

然后看一下他们是怎么样被调用的:

asyncFunc1()
  .then(asyncFunc2)
  .then(asyncFunc3)
  .then(asyncFunc4)
  .then(asyncFunc5);

这样,这些异步函数就会按照顺序一个一个依次执行了。

ES6中原生支持了Promise,不过在原生不支持Promise的环境中,我们有很多第三方库来支持,比如Q.js和Bluebird。它们一般都除了提供标准Promise的API外,还提供了一些标准之外但非常有用的API,使得异步流程的控制更加优雅。

从微信小程序的API文档中我们可以看到,框架提供的JavaScript API中很多函数其实都是异步的,如wx.setStorage(), wx.getStorage(), wx.getLocation()等等,它们也是提供的回调的处理方式,在参数中传入success, fail,complete回调函数,就可以对运行成功或失败进行分别处理。

如:

wx.getLocation({ 
  type: 'wgs84', 
  success: function(res) { 
    var latitude = res.latitude 
    var longitude = res.longitude 
    var speed = res.speed 
    var accuracy = res.accuracy 
  },
  fail: function() {
    console.error("get location failed")
  }
})

我们能不能让微信小程序的异步API支持Promise呢?答案是肯定的,我们当然可以一个一个的用Promise去包装这些API,但是这个还是比较麻烦的。不过,由于小程序的API的参数格式都比较统一,只接受一个object参数,回调都是在这个参数中设置,所以,这为我们的统一处理提供了便利,我们可以写一个非侵入性的工具方法,来完成这样的工作:

假设我们将这个工具方法写到一个名为的util.js的文件中:

var Promise = require('../libs/bluebird.min')  //我用了bluebird.js

/**
 * 将小程序的API封装成支持Promise的API
 * @params fn {Function} 小程序原始API,如wx.login
 */
function wxPromisify(fn) {  
  return function (obj = {}) { 
    return new Promise((resolve, reject) => {      
      obj.success = function (res) {        
        resolve(res)      
      }      

      obj.fail = function (res) {        
        reject(res)      
      }      

      fn(obj)    
    })  
  }
}

module.exports = {  
  wxPromisify: wxPromisify
}

之后,我们来看一下如何使用这个方法,将原来回调方式的API变成Promise的方式:

var util = require('../utils/util')

var getLocationPromisified = util.wxPromisify(wx.getLocation)

getLocationPromisified({
  type: 'wgs84'
}).then(function (res) {
  var latitude = res.latitude 
  var longitude = res.longitude 
  var speed = res.speed 
  var accuracy = res.accuracy 
}).catch(function () {
  console.error("get location failed")
})

是不是很容易理解?

关于使用Promise处理异步流程,就先讲到这里,有什么疑问,可以留言给我。不对之处,欢迎指正。

谢谢大家阅读本文。

相关文章

网友评论

  • 6575c56cf1a1:需要明确说明:
    asyncFunc1()
    .then(asyncFunc2)
    .then(asyncFunc3)
    .then(asyncFunc4)
    .then(asyncFunc5);
    这里并不会等待每个then体内的异步执行完成后再执行下一个then体;

    测试代码如下:
    function testPromise(state,num){
    return new Promise(function(resolve, reject){

    if(state==1)
    {
    console.log("resolve oninit:"+num);
    setTimeout(function(){
    resolve();
    console.log("resolve:"+num);
    },num);

    }
    else
    {
    reject();
    console.log("reject:"+num);
    }
    });
    }

    testPromise(1,0)
    .then(testPromise(1,4000))
    .then(testPromise(1,2000))
    .then(testPromise(1,1000))

    //输出:
    // test.html:1810 resolve oninit:0
    // test.html:1810 resolve oninit:4000
    // test.html:1810 resolve oninit:2000
    // test.html:1810 resolve oninit:1000
    // test.html:1813 resolve:0
    // test.html:1813 resolve:1000
    // test.html:1813 resolve:2000
    // test.html:1813 resolve:4000
    一斤代码:@愚笨的村长 你要理解这一点:resolve和reject在基本行为上都是一致的,用法上也是一致的,唯一不一样的是,resovle将结果导向到下一个then,而reject将结果导向下一个catch。

    这里这个视频的前半个小时有讲解Promise的用法,你可以了解下:https://www.bilibili.com/video/av25143408
    6575c56cf1a1:@一斤代码 感谢答复。刚刚接触到promise链式调用,确实不懂。

    我所理解你的意思是在resolve中返回了要继续执行的promise,同理也可以同样给失败reject时指定要继续执行的promise;

    相当于:
    testPromise(1,0)
    .then(function(){return testPromise(0,4000)})
    .then(null,function(){return testPromise(1,2000)})
    .then(function(){return testPromise(1,1000)}) ;

    1.如果说需要在resolve来返回新的promise,那为什么在执行reject()时,不在前面加上 return reject(); 呢?
    一斤代码:我只能说,那是因为你写错了😁。你的调用应该改成这样:

    testPromise(1, 0)
    .then(() => testPromise(1, 4000))
    .then(() => testPromise(1, 2000))
    .then(() => testPromise(1, 1000))

    自己再体会一下。
  • e65ae8d9c4ff:var flag = false;
    ......
    getLocationPromisified({
    type: 'wgs84'
    }).then(function (res) {
    var latitude = res.latitude; // 纬度,浮点数,范围为90 ~ -90
    var longitude = res.longitude; // 经度,浮点数,范围为180 ~ -180。
    var speed = res.speed; // 速度,以米/每秒计
    var accuracy = res.accuracy; // 位置精度
    geocoder = new qq.maps.Geocoder({
    complete: function (result) {
    var province = result.detail.addressComponents.province
    var city = result.detail.addressComponents.city;
    var district = result.detail.addressComponents.district
    var location = "" + province + city + district;
    flag = true;
    $("input[name='location']").val(location);
    loadProduct(location, productLabel, productBrand, searchKeyword, order, pageNo);
    }
    });
    var coord = new qq.maps.LatLng(Number.parseFloat(latitude), Number.parseFloat(longitude));
    geocoder.getAddress(coord);
    }).then(function(){
    if(!flag) {
    loadProduct("", productLabel, productBrand, searchKeyword, order, pageNo);
    }
    })
    为什么先执行了第二个then,然后在执行第一个
    一斤代码:因为你第一个then里,又发起了一个异步请求却没有正确的处理它,所以就直接执行下去,跑进了下一个then
  • Elliot_cc60:为什么必须引入库???
    一斤代码:现在已经不是必须的了。写文章的时候时间比较早,小程序环境已经变更了好几回了,以前小程序环境移除了原生Promise对象,所以必须引入库,现在加回来了,所以不需要引入了。
  • 5a61e4fee73f:新人。。。我现在想在让page.onLoad在app.onLaunch之后执行,因为app.onLaunch有api会执行比较慢,请问应该怎么写。
    onLoad: function (options) {
    var getLocationPromisified = cusPromise.wxPromisify(app.onLaunch())

    getLocationPromisified().then(function (res) {
    console.log(res)
    // var latitude = res.latitude
    // var longitude = res.longitude
    // var speed = res.speed
    // var accuracy = res.accuracy
    }).catch(function () {
    console.error("什么错误")
    })
    },
    这里的:var getLocationPromisified = cusPromise.wxPromisify(FUN)。FUN应该怎样递。谢谢大神
    5a61e4fee73f:@LIXIAOLONG_4682 另外我写成这样:
    var getUserAutoLogin= util.wxPromisify(app.getUserAutoLogin())
    则会直接走到 .catch()
    5a61e4fee73f:@一斤代码 这里我把方法改了一下:
    onLoad: function (options) {

    var getUserAutoLogin= util.wxPromisify(app.getUserAutoLogin)
    console.log(getUserAutoLogin)
    }
    打印出来的getUserAutoLogin为:
    ƒ () {
    var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

    return new Promise(function (resolve, reject) {
    obj.success = function (res) {
    resolve(re…

    此时应该说明已经返回了Promise了吧,然后我下面调用:
    getUserAutoLogin().then(function (res) {
    console.log(1111)
    this.setData({
    userInfo:app.globalData
    })
    }).catch(function () {
    console.error("get location failed")
    })
    但是 打印不出1111,请问是哪里出了问题
    一斤代码:app的onLaunch()方法是生命周期方法,不要用app.onLaunch()显式去调用。像你这种情况,可以参考小程序开发工具里创建新小程序工程的那个模板工程的代码,里面有个userInfoReadyCallback的用法,那个就是用来解决你说的这种情况的。不过它这个是传callback函数的方式,你如果要用Promise的话,稍微改一下,传一个能返回Promise的函数就行了。
  • 这是一只喵:大神 wxPromisify 内部的封装不是很懂,可以讲下么。
    一斤代码:wxPromisify函数的原理很简单,就是将小程序的原生API(wx.login, wx.request之类)封装成支持Promise的API形式。

    这个函数内部其实就是创建一个简单的Promise并在该Promise中执行传入的原生API。在执行过程中,传递给原生API的obj参数中的回调函数success和fail会被我们的覆盖(见代码,调用resolve和reject来导出执行结果),所以在调用这个Promise后的then和catch中,我们就能获取到原生API的执行结果了。
  • 70d2263fdff4:群主,问个问题哈,我在app.js立面弄了一个wxLogin函数

    我想在这个wxLogin函数执行完毕后,执行一段代码,我该怎么写呢?
    一斤代码:你是用util.wxPromisify包装的wxLogin是吧?如果是这样的话,那么就是这样写:

    var wxLogin = util.wxPromisify(wx.login)
    wxLogin().then(function(res) {
    console.log("获取到的code: ", res.code)
    // 你的下一端代码逻辑......
    }).then(function(){
    // 你的另一端代码逻辑......
    })
  • c0fbb5b568ad:我的为什么执行顺序是随机的感觉,总是不能按照login-userinfo-register的顺序执行 。
    var login = util.wxPromisify(app.login());
    var userInfoFun = util.wxPromisify(app.userInfoFun());
    var register = util.wxPromisify(app.register());

    console.log("login=", login);
    login().then(function (res) {
    }).then(userInfoFun).then(function (res) {

    }).then(register).then(function(res){
    })
    dreamruner:@幸福微甜_1b23 加括号是方法调用,不加括号是参数传递,这个还是要区别开来,我刚接触的时候也是容易弄混
    一斤代码:@幸福微甜_1b23 你这里写错了,app.login,app.userInfoFun,app.register后面不要加括号。

    var login = util.wxPromisify(app.login);
    var userInfoFun = util.wxPromisify(app.userInfoFun);
    var register = util.wxPromisify(app.register);

    仔细体会吧:)
    c0fbb5b568ad:userinfo是userInfoFun,写错了上面的文字
  • cbbf79988d6b:你好 大腿 请问 有多个ruquest请求 应该怎么写呀?
    cbbf79988d6b:@一斤代码 好的 谢谢大腿
    一斤代码:var wxRequest = util.wxPromisify(wx.request)

    wxRequest({
    url: 'https://...........'
    }).then(function (res) {
    // 处理第一个请求结果...
    }).then(function () {
    return wxRequest({
    url: 'https://...........'
    })
    }).then(function (res) {
    // 处理第二个请求的结果...
    })

  • 抓住时间的尾巴吧:有多个方法的情况下,怎么按顺序执行我的方法?
    var fun1 = util.wxPromisify(that.fun1())
    var fun2 = util.wxPromisify(that.fun2())

    fun1().then(function (res) {
    console.log(res)
    }).catch(function (res) {
    console.log(res)
    console.error("get location failed")
    })

    fun2().then(function (res) {
    console.log(res)
    }).catch(function (res) {
    console.log(res)
    console.error("get location failed")
    })
    一斤代码:@幸福微甜_1b23 完全可以的,你也可以这样写:

    fun1().then(function (res) {
    console.log(res)
    }).then(function () {
    return fun2().then(function (res) {
    console.log(res)
    })
    }).catch(function (err) {
    console.log(err)
    console.error("get location failed")
    })
    c0fbb5b568ad:@一斤代码 fun2的内容一定要写在func2.then里面吗?可以调用方法吗在里面
    一斤代码:你是要顺序的执行fun1和fun2是么?这样的话你可以像这样来写:
    fun1().then(function (res) {
    console.log(res)
    }).then(fun2).then(function (res) {
    console.log(res)
    }).catch(function (err) {
    console.log(err)
    console.error("get location failed")
    })
  • 萨默塞特酱:请问下为何:var getLocationPromisified = util.wxPromisify(wx.getLocation);
    这么写可以生效;
    而先promisefy,然后export出去:
    function Login() {
    return wxPromisify(wx.login);
    }

    module.exports = {
    wxPromise: wxPromisify,
    Login: Login
    }
    在其他地方调用就会报错:
    wc.Login(...).then is not a function;
    抓住时间的尾巴吧:有多个方法的情况下,怎么按顺序执行我的方法?
    var fun1 = util.wxPromisify(that.fun1())
    var fun2 = util.wxPromisify(that.fun2())

    fun1().then(function (res) {
    console.log(res)
    }).catch(function (res) {
    console.log(res)
    console.error("get location failed")
    })

    fun2().then(function (res) {
    console.log(res)
    }).catch(function (res) {
    console.log(res)
    console.error("get location failed")
    })
    萨默塞特酱:@一斤代码 谢谢,用了方法二可以了,我去想想为什么这样就可以:smile:
    一斤代码:@萨默塞特酱 你这里有一个错误的地方,要改正有两种方式:

    方式一:调用的时候应该写成:
    wc.Login()(...).then(....)

    var loginPromisified = wc.Login()
    loginPromisified(...).then(...)

    方式二:把function Login的定义改一下,改成:
    function Login(params) {
    return wxPromisify(wx.login)(params)
    }

    然后调用的时候可以:wc.Login(...).then(...)

  • 6beba5d4a569:我的bluebird.min.js文件是从官网弄来的,是不是这个文件有问题,能发个你的版本给我么
    一斤代码:@wulh8410 bluebird在安卓环境有问题。你看这篇文章吧,用es6-promise替代:http://www.jianshu.com/p/d29746bf29e3
  • 6beba5d4a569:群主,按你所写的,我搬过去后会报这个错误:WAService.js:4 Uncaught Error: can't find module : ../../bluebird.min 这是什么问题?不支持了?
  • 石野小真人:感觉promise类似于rxjava,有木有
  • E2vHLi:刚刚更新了ide发现Promise报错了,难道不支持了 WAService.js:3 TypeError: Promise is not a constructor
    一斤代码:@user11402 最新的IDE里已经把原生的Promise去掉了,需要使用第三方Promise实现。比如bluebird.js :smile:
  • 一斤代码:我们的高质量前端开发者微信群【前端精修社】欢迎前端开发者加入:
    http://www.jianshu.com/p/d9a0202bd257
    嗯2018:大腿能加个微信啦一下小程序开发群吗,,, 新入坑求救
    一斤代码:@Ccc小丸子 加我微信吧:zarknight,拉你入群
    执着于98斤的it女:这个群 过期了~

本文标题:微信小程序中使用Promise进行异步流程处理

本文链接:https://www.haomeiwen.com/subject/wskvuttx.html