未分类

    探索Promise用法和机制

    以前就会经常用到 Promise,但是没有去探究内部的实现机制。 正好由这次在小程序中引入了 Promise ,探究下内部原理。

    promise


    为什么使用 Promise

    Promise 可以让我们避免回调的地狱。以前我们可能使用的是 bluebird 或者 Q,现在我们已经有了原生的实现。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    func1(function (value1) {
    func2(value1, function (value2) {
    func3(value2, function (value3) {
    func4(value3, function (value4) {
    func5(value4, function (value5) {
    // Do something with value 5
    });
    });
    });
    });
    });

    使用 Promise 后,可以把平行的代码变成竖状的易读的代码。

    1
    2
    3
    4
    5
    func1(value)
    .then(func2)
    .then(func3)
    .then(func4)
    .then(func5)

    一个最常见的 Promise 的例子如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var request = require('request');
    return new Promise((resolve, reject) => {
    request.get(url, (error, response, body) => {
    if(body) {
    resolve(JSON.parse(body));
    } else {
    resolve({}); //reject(....);
    }
    })
    })


    小程序 Promise 化实现

    小程序里,包括官方文档的 demo 里会看到很多的 cb, 比如我们获取用户身份信息就需要下面的操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    wx.login({
    success: function () {
    wx.getUserInfo({
    success: function (res) {
    that.globalData.userInfo = res.userInfo;
    typeof cb == "function" && cb(that.globalData.userInfo)
    }
    })
    }
    });

    这样略微深的嵌套一定程度的阻碍了我们的阅读和理解,代码整体也不好查 bug 和 扩展。所以我考虑到用 Promise 扩展一层。

    但是由于小程序已经去除了 自带的 Promise ,所以需要开发者自动引入 Promise 库,或者编写相应的 Promise 库。这里我引入 es6-promise 这个库。注意不要使用 bluebird ,bluebird 会导致android 上有报错,因为这个里面有用到一些小程序不支持的比如 document, window之类的对象。

    选取一小段没使用的代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var Promise = require('es6-promise').Promise;
    var wxLib = require('../wxLib);
    wxLib.login()
    .then(()=>{
    wxLib.getUserInfo();
    })
    .catch(()=>{
    wxLib.showErrorMsg();
    })

    promise 更易我们看清整个的结构。更好的控制异步流程, 也可以让我们使用本来不方便使用的 return, throw 等。


    更多其他的用法

    关于 Promise 的几个注意的地方

    1. Promise 对象是一个构造函数,所以才需要 new Promise 生成一个 Pormise 的实例对象。
    2. Promise 构造函数接受了两个参数,分别是 resolve 和 reject, 这两个函数由 js 引擎提供,不需要自己实现。
    3. resolve 是将 Promise 对象从 未完成 => 成功。(pending => resolved), reject 是 将对象的状态从 未完成=> 失败。(pending=>rejected)
    4. then 方法接收两个回调函数。第一个是 Promise 返回 resolved 的时候调用的, 第二个是 返回 rejected 的时候调用的。
    5. Promise catch 是 .then(null, rejection)的别名,里面的回调用于发生错误时使用。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 这种情况不会捕捉then 里的错误
    promise
    .then(function(data) {
    // success
    }, function(err) {
    // error
    });
    // better
    promise
    .then(function(data) { //cb
    // success
    })
    .catch(function(err) {
    // error
    });

    关于Promise.all的用法

    var p = Promise.all([p1, p2, p3]);
    其中 Promise.all 会接收一个数组作为参数, p1,p2,p3都是 Promise 对象的实例。 如果不是,则调用 Promise.resolve 方法,将参数转化为 Promise 实例。

    他们三个之间的关系是: 必须都变成 fulfilled , p才是 fulfilled, 只要有一个是 rejected, 就会是 rejected, 并且这个第一个被 reject 的实例的返回值就会给到 P。 haha, 挺团结的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    let urls = [
    '/api/commits',
    '/api/issues/opened',
    '/api/issues/assigned',
    '/api/issues/completed',
    '/api/issues/comments',
    '/api/pullrequests'
    ];
    let promises = urls.map((url) => {
    return new Promise((resolve, reject) => {
    $.ajax({ url: url })
    .done((data) => {
    resolve(data);
    })
    })
    })
    Promise.all(promises)
    .then((results) => {
    // results is an array list
    })

    有一个方法叫做 Promise.race() 跟 Promise.all 差不多。也是

    var p = Promise.race([p1, p2, p3]);

    p1, p2, p3 只要有一个率先改变,p 的状态就会改,并且把这个率先改的返回值给到 P。


    Promise.resolve() 的 用法

    上面提到了, promise.all 和 promise.race 的参数 p1, p2, p3 都必须是 promise 的实例对象。 如果不是,就需要转化为 Promise 对象。 Promise.resolve 就派上了用法。

    1
    2
    Promise.resolve('foo');
    new Promise(resolve => resolve('foo'));

    Promise.resolve 方法的参数分成下面几个情况:

    Promise.resolve(value);

    Promise.resolve(promise);

    Promise.resolve(thenable);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Promise.resolve("Success").then(function(value) {
    console.log(value); // "Success"
    }, function(value) {
    // 不会被调用
    });
    var p = Promise.resolve([1,2,3]);
    p.then(function(v) {
    console.log(v[0]); // 1
    });

    实现一个简易的 Promise

    这里实现的过程我参考阅读了很多篇文章,感谢,具体有:

    1. 剖析Promise内部结构
    2. how-do-promises-work
    3. JS Promise的实现原理

    先搭一个简单的框架。


    构造函数的实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // processor 就是传给 Promise 的函数
    function Promise(processor) {
    this.status = 'pending';
    this.data = undefined;
    this.onResolvedCb = [];
    this.onRejectedCb = [];
    this.resolve = (value) => {
    //TODO
    }
    this.reject = (reason) => {
    //TODO
    }
    try{
    (typeof processor === 'function') && processor(resolve, reject);
    }catch(e) {
    reject(e);
    }
    }

    然后是对 resolve 和 reject 的实现, 我们用原生的 Promise 的时候不需要实现这两个函数,是因为 JS 引擎已经帮我们做了这件事情。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    this.resolve = (value) => {
    if(this.status === 'resolved' || this.status === 'pending') {
    this.status = 'resolved';
    this.data = value;
    for(var i = 0, l = this.onResolvedCb.length; i < l; i++) {
    //执行回调函数
    this.onResolvedCb[i].value;
    }
    }
    }
    // reject 就和 resolve 非常像
    this.reject = (reson) => {
    if(this.status === 'rejected' || this.status === 'pending'){
    this.status = 'rejected';
    //......
    }
    }

    then 函数的实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    Promise.prototype.then = (onResolved, onRejected) => {
    onResolved = typeof onResolved === 'function' ? onResolved : function(value) {}
    onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {}
    if(this.status === 'resolved'){
    return new Promise((resolve, reject) => {
    try{
    let x = onResolved(self.data);
    if(x instanceof Promise) {
    x.then(resolve, reject);
    }
    resolve(x);
    } catch (e) {
    reject(e);
    }
    })
    }else if(this.status === 'rejected') {
    return new Promise((resolve, reject) => {
    //TODO
    })
    }else{
    //pending
    return new Promise((resolve, reject) => {
    //TODO
    })
    }
    }

    总结

    上面只是一个粗浅的大概。如果要丰富 Promise , 还要去实现很多其他的内容,比如 catch 之类的。关于异步还有很多要学习,其实大部分都学习过,只是因为使用的少,没有考虑他内部的实现,也比较容易忘记。还是实践是王道。