深入理解 Promise (下)

阅读 937
收藏 159
2016-12-05
原文链接:coderlt.coding.me

经过几天源码研究学习之后,基本上对Promise有了深入的了解,也手动封装了自己了Promise工具类,下面就是我们去在应用场景中去验证这个工具类的使用了

上 - 理论知识

  • Promise 规范
  • ES6 Promise API
  • Polyfill和扩展类库
  • Promise 在应用中的错误用法和误区
    • 当作回调使用
    • 没有返回值
    • 没有catch
    • catch()与then(null, onRejected)
    • 断链
    • 穿透
    • 长度未知的串行与并行
    • Promise.resolve的使用
  • 最佳实践

中 - 手动封装

  • 实现一个简单的 Promise工具类
    • Promise类的结构
    • 构造器的初始化
    • then方法
    • catch方法
  • 添加扩展功能函数
    • all
    • race
    • resolve
    • reject
    • wait
    • stop
    • always
    • done
    • defer
    • timeout
    • sequence
  • 测试
  • 源码

下 - 实践应用

  • 结合应用场景使用Promise
    • 使用Promise编写Web Notifications提示
    • 使用Deferred封装异步请求
    • 异步请求的超时处理
    • 基于Promise的fs方法链

Web Notifications提示

显示桌面通知的流程如下:

  1. 用户进入页面要判断是否默认允许显示桌面通知
  2. 如果不允许,提示是否允许
  3. 如果点击了允许,则显示通知
  4. 如果点击了拒绝,则不显示,后面的任务不执行
  5. 显示通知失败,则打印显示失败的信息
  6. 显示成功,判断用户是否点击了通知或点击了关闭
  7. 如果点击了,则关闭通知
  8. 如果没有点击,则2s之后自动关闭
  9. 关闭之后打印消息

代码逻辑

const msgOpt = {
    body:'你今天还有需要完成的任务哦!',
    icon:'http://7xi480.com1.z0.glb.clouddn.com/avatar100.jpg'
}
// 是否允许显示桌面通知
function requestNoti(){
    return new MPromise((resolve, reject)=>{
        if(Notification.permission === 'granted'){
            resolve()
        }else{
            Notification.requestPermission(function (status) {
                if (Notification.permission !== status) {
                    Notification.permission = status;
                }
                if (status === 'granted') {
                    resolve()
                } else {
                    reject(new Error('user denied'));
                }
            });
        }
    })
}
// 显示通知
function showNoti(msgObj){
    return new MPromise((resolve, reject)=>{
        var n = new Notification('通知',msgObj)
    
        // 3s钟之内,无论是用户点击还是超时未点击关闭,都将关闭通知
        MPromise.timeout(closeNoti(), 3000).always(()=>{
            n.close();
            resolve();
        })
    
        // 如果打开失败 reject 触发rejected回调
        n.addEventListener('error', reject)
    
        function closeNoti(){
            return new MPromise(rs => {
                n.addEventListener('click', rs)
                n.addEventListener('close', rs)
            })
        }
    })
}
// 开始
requestNoti()
    .then(()=>{
        console.log('显示 桌面通知')
        return showNoti(msgOpt)
    })
    .catch(()=>{
        console.log('中断 不允许显示桌面通知')
        return MPromise.stop();
    })
    .then(()=>{
        console.log('关闭 桌面通知')
    })
    .catch(err=>{
        console.log('失败 桌面通知打开失败')
    })

这里API的应用
timeout() 用于限制2s钟之内,通知必须关闭
always() 与timeout结合使用
stop() 在promise链的中途停止后面的执行

使用Deferred封装异步请求

这里为什么使用Deferred来封装异步请求呢?
因为使用 new Promise() 的形式会有多一层嵌套,使用 deferred 可以对流程控制自由定制

function ajaxGet(URL) {
    var deferred = MPromise.deferred();
    var req = new XMLHttpRequest();
    req.open('GET', URL, true);
    req.onload = function () {
        if (req.status === 200) {
            deferred.resolve(req.responseText);
        } else {
            deferred.reject(new Error(req.statusText));
        }
    };
    req.onerror = function () {
        deferred.reject(new Error(req.statusText));
    };
    req.send();
    
    var abort = function () {
        if (req.readyState !== XMLHttpRequest.UNSENT) {
            req.abort();
        }
    };
    return {
        promise: deferred.promise,
        abort: abort
    }
}
ajaxGet('d1.json').promise
    .then(res=>JSON.parse(res))
    .catch(err=>console.log(err))
    .then(res=>console.log(res))

异步请求的超时处理

发起一个异步请求,如果3s内还没有请求成功,则取消请求

var getData = ajaxGet('d1.json');
console.log('显示loading')
MPromise.timeout(getData.promise, 3000)
    .then(res=>{
        return JSON.parse(res);
    }, err=>{
        if(err instanceof window.TimeoutError){
            getData.abort();
            throw new Error('请求超时,取消请求')
        }else{
            throw err;
        }
    })
    .then(res=>{ console.log('请求成功: ', res); })
    .always(()=>{ console.log('取消loading') })
    .catch(err=>{ console.log(err) })

基于Promise的fs方法链

使用Promise对fs封装的好处是可以很方便的异步处理文件流,对错误可以集中式处理,如果是同步使用fs相关方法,错误处理将会变得复杂

var fs = require("fs");
var MPromise = require('./promise_browser');
function File() {
    this.promise = MPromise.resolve();
}
// Static method for File.prototype.read
File.read = function (filePath) {
    var file = new File();
    return file.read(filePath);
};
File.prototype.then = function (onFulfilled, onRejected) {
    this.promise = this.promise.then(onFulfilled, onRejected);
    return this;
};
File.prototype["catch"] = function (onRejected) {
    this.promise = this.promise.catch(onRejected);
    return this;
};
File.prototype.read = function (filePath) {
    return this.then(function () {
        return fs.readFileSync(filePath, "utf-8");
    });
};
File.prototype.transform = function (fn) {
    return this.then(fn);
};
File.prototype.write = function (filePath) {
    return this.then(function (data) {
        return fs.writeFileSync(filePath, data)
    });
};
module.exports = File;

使用

var File = require("./fs-promise-chain");
var inputFilePath = "input.txt",
    outputFilePath = "output.txt";
File.read(inputFilePath)
    .transform(function (content) {
        return ">>" + content;
    })
    .write(outputFilePath)
    .catch(function(err){
        console.log(err)
    })

对于 nodejs 相关的异步处理,Qbluebirde 都有相关的API用于包装成Promise对象,

例如 bluebirde 中可以使用 Promise.promisify 方法进行包装

var readFile = Promise.promisify(require("fs").readFile);
readFile("myfile.js", "utf8").then(function(contents) {
    return eval(contents);
}).then(function(result) {
    console.log("The result of evaluating myfile.js", result);
}).catch(SyntaxError, function(e) {
    console.log("File had syntax error", e);
//Catch any other error
}).catch(function(e) {
    console.log("Error reading file", e);
});

这里还是建议大家使用 Q 或者 bluebirde 等相关成熟的Promise方案,本系列的Promise学习就到这里的,在此过程中封装的Promise工具类也主要用于学习和理解Promise的原理。

重点是我们掌握了Promise的使用之后,能更好的和ES7 的 Async/await 结合起来使用,那么以后的异步处理就更加得心应手了。

据说 node7.0 已经支持 Async/await ,让我们视目以待吧。

相关示例源码,请至 MPromise 查看

评论