ES6 Promise的使用和理解

栏目: JavaScript · 发布时间: 7年前

内容简介:JS语言的执行环境是“单线程”的,即指一次只能完成一件任务;如果有多个任务,那么必须排队,前面一个任务完成,再执行后一个任务,以此类推。这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。为了解决这个问题,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步

JS语言的执行环境是“单线程”的,即指一次只能完成一件任务;如果有多个任务,那么必须排队,前面一个任务完成,再执行后一个任务,以此类推。这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行。

为了解决这个问题,Javascript语言将任务的执行模式分成两种:同步(Synchronous)和异步(Asynchronous)。

"同步模式"就是上一段的模式,后一个任务等待前一个任务结束,然后再执行,程序的执行顺序与任务的排列顺序是一致的、同步的;"异步模式"则完全不同,每一个任务有一个或多个回调函数(callback),前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行,所以程序的执行顺序与任务的排列顺序是不一致的、异步的。

"异步模式"非常重要。在浏览器端,耗时很长的操作都应该异步执行,避免浏览器失去响应,最好的例子就是Ajax操作。在服务器端,"异步模式"甚至是唯一的模式,因为执行环境是单线程的,如果允许同步执行所有http请求,服务器性能会急剧下降,很快就会失去响应。

常用的异步编程模式

  1. 回调函数
    即f1,f2两个函数,f2要等待f1执行结果后执行,即 f1(f2)
  2. 事件驱动的方式
    f1.on('done', f2); (JQ写法,f1完成时,trigger("done")则执行f2)
  3. 发布-订阅 模式
  4. Promise对象实现

Promise对象

本文着重讲ES6的Promise对象的定义和用法 阮一峰老师的ES6详解 - Promise对象 相信大家在学习ES6的过程中都或多或少的学习过阮老师的ES6教程,那么这里简单举一些例子讲述Promise对象的特点和使用方法

基础使用方法

ES6提供Promise构造函数,我们创造一个Promise实例,Promise构造函数接收一个函数作为参数,这个传入的函数有两个参数,分别是两个函数 resolvereject 作用是, resolve 将Promise的状态由未成功变为成功,将异步操作的结果作为参数传递过去;相似的是 reject 则将状态由未失败转变为失败,在异步操作失败时调用,将异步操作报出的错误作为参数传递过去。

实例创建完成后,可以使用 then 方法分别指定成功或失败的回调函数,比起f1(f2(f3))的层层嵌套的回调函数写法,链式调用的写法更为美观易读

let promise = new Promise((resolve, reject)=>{
    reject("拒绝了");
});
promise.then((data)=>{
    console.log('success' + data);
}, (error)=>{
    console.log(error)
});

执行结果:"拒绝了"

复制代码

Promise的特点

  • 对象不受外界影响,初始状态为pending(等待中),结果的状态为resolve和reject,只有异步操作的结果决定这一状态
  • 状态只能由pending变为另外两种的其中一种,且改变后不可逆也不可再度修改,
    即pending -> resolved 或 pending -> reject
let promise = new Promise((resolve, reject)=>{
    reject("拒绝了");
    resolve("又通过了");
});
promise.then((data)=>{
    console.log('success' + data);
}, (error)=>{
    console.log(error)
});

执行结果: "拒绝了"

复制代码

上述代码不会再执行resolve的方法

then方法的规则

  • then 方法下一次的输入需要上一次的输出
  • 如果一个promise执行完后 返回的还是一个promise,会把这个promise 的执行结果,传递给下一次 then
  • 如果 then 中返回的不是Promise对象而是一个普通值,则会将这个结果作为下次then的成功的结果
  • 如果当前 then 中失败了 会走下一个 then 的失败
  • 如果返回的是undefined 不管当前是成功还是失败 都会走下一次的成功
  • catch是错误没有处理的情况下才会走
  • then 中不写方法则值会穿透,传入下一个 then

用node fs模块读取文件的流程来测试 我们创建一个读取文件的方法,在Promise中定义如果读取成功则展示文件的内容,否则报出错误

let fs = require('fs');

function read(file, encoding) {
    return new Promise((resolve, reject)=>{
        fs.readFile(filePath, encodeing, (err, data)=> {
            if (err) reject(err);
            resolve(data);
        });
    })
}
复制代码

由于想看到多次连贯回调,我们专门设置3个txt文件,其中1号文件的内容为2号文件的文件名,2号文件的内容为3号文件的文件名,3号中展示最终内容

ES6 Promise的使用和理解

执行代码如下:

read('1.promise/readme.txt', 'utf8').then((data)=>{
    console.log(data)
});
复制代码

读取一个文件的打印结果为,readme2.txt 我们改造这个代码,添加多个回调,在最后一个之前的所有then中都return出当前返回的promise对象

read('readme.txt', 'utf8').then((data)=>{
    return read(data, 'utf8');
}).then((data)=>{
    return read(data, 'utf8')
}).then((data)=>{
    console.log(data);
});

最终输出 readme3.txt的内容

复制代码
ES6 Promise的使用和理解
ES6 Promise的使用和理解

再对下一步then进行新的处理,我们对readme3.txt的内容进行加工并返回

read('readme.txt', 'utf8').then((data)=>{
    return read(data, 'utf8');
}).then((data)=>{
    return read(data, 'utf8')
}).then(data=>{
    return data.split('').reverse().join();
}).then(null,data=>{
    throw new Error('出错')
}).then(data=>{
    console.log(data)
});
复制代码

这里我们将内容处理后,则将一个普通值传给了下次的then作为公共的过,然后在下一步中没有处理成功,

ES6 Promise的使用和理解

那么再传入下一个then,最终会作为成功值打印出来。

最后我们看一下对错误的处理,在处理完readme3.txt的结果后,我们将这个值传入下一个then中,令其作为一个文件名打开,然而此时已经找不到这个不存在的文件了,那么在最后一步就会打印出报错的结果

read('readme.txt', 'utf8').then((data)=>{
    return read(data, 'utf8');
}).then((data)=>{
    return read(data, 'utf8')
}).then(data=>{
    return data.split('').reverse().join();
}).then(null,data=>{
    throw new Error('出错')
}).then(data=>{
    return read(data, 'utf8')
}).then(null,(err)=>{
    console.log(err)
});
复制代码

结果:

ES6 Promise的使用和理解

Promises A+ (Promises Aplus)

Promises Aplus规范即规定了Promise的原理,源代码规范等,通过这个规范,我们可以自己实现一个基于PromiseA+规范的Promise类库,下面我们展示一下源码的实现

/**
 * Promise 实现 遵循promise/A+规范
 * 官方站: https://promisesaplus.com/
 * Promise/A+规范译文:
 * https://malcolmyu.github.io/2015/06/12/Promises-A-Plus/#note-4
 */

// promise 三个状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

function Promise(excutor) {
    let self = this; // 缓存当前promise实例对象
    self.status = PENDING; // 初始状态
    self.value = undefined; // fulfilled状态时 返回的信息
    self.reason = undefined; // rejected状态时 拒绝的原因
    self.onFulfilledCallbacks = []; // 存储fulfilled状态对应的onFulfilled函数
    self.onRejectedCallbacks = []; // 存储rejected状态对应的onRejected函数

    function resolve(value) { // value成功态时接收的终值
        if(value instanceof Promise) {
            return value.then(resolve, reject);
        }

        // 为什么resolve 加setTimeout?
        // 2.2.4规范 onFulfilled 和 onRejected 只允许在 execution context 栈仅包含平台代码时运行.
        // 这里的平台代码指的是引擎、环境以及 promise 的实施代码。实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。

        setTimeout(() => {
            // 调用resolve 回调对应onFulfilled函数
            if (self.status === PENDING) {
                // 只能由pedning状态 => fulfilled状态 (避免调用多次resolve reject)
                self.status = FULFILLED;
                self.value = value;
                self.onFulfilledCallbacks.forEach(cb => cb(self.value));
            }
        });
    }

    function reject(reason) { // reason为失败态时接收的原因
        setTimeout(() => {
            // 调用reject 回调对应onRejected函数
            if (self.status === PENDING) {
                // 只能由pedning状态 => rejected状态 (避免调用多次resolve reject)
                self.status = REJECTED;
                self.reason = reason;
                self.onRejectedCallbacks.forEach(cb => cb(self.reason));
            }
        });
    }

    // 捕获在excutor执行器中抛出的异常
    // new Promise((resolve, reject) => {
    //     throw new Error('error in excutor')
    // })
    try {
        excutor(resolve, reject);
    } catch (e) {
        reject(e);
    }
}


复制代码

这一部分代码我们对resolve和reject进行了判断处理,接着我们构造 then 方法

/**
 * [注册fulfilled状态/rejected状态对应的回调函数]
 * @param  {function} onFulfilled fulfilled状态时 执行的函数
 * @param  {function} onRejected  rejected状态时 执行的函数
 * @return {function} promise2  返回一个新的promise对象
 */
Promise.prototype.then = function (onFulfilled, onRejected) {
    // 成功和失败的回调 是可选参数
    
    // onFulfilled成功的回调 onRejected失败的回调
    let self = this;
    let promise2;
    // 需要每次调用then时都返回一个新的promise
    promise2 = new Promise((resolve, reject) => {
    // 成功态
        if (self.status === 'resolved') {
            setTimeout(()=>{
                try {
                    // 当执行成功回调的时候 可能会出现异常,那就用这个异常作为promise2的错误的结果
                    let x = onFulfilled(self.value);
                    //执行完当前成功回调后返回结果可能是promise
                    resolvePromise(promise2,x,resolve,reject);
                } catch (e) {
                    reject(e);
                }
            },0)
        }
        // 失败态
        if (self.status === 'rejected') {
            setTimeout(()=>{
                try {
                    let x = onRejected(self.reason);
                    resolvePromise(promise2,x,resolve,reject);
                } catch (e) {
                    reject(e);
                }
            },0)
        }
        if (self.status === 'pending') {
           // 等待态时,当一部调用resolve/reject时,将onFullfilled/onReject收集暂存到集合中 self.onResolvedCallbacks.push(() => {
                setTimeout(()=>{
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2,x,resolve,reject);
                    } catch (e) {
                        reject(e);
                    }
                },0)
            });
            self.onRejectedCallbacks.push(() => {
                setTimeout(()=>{
                    try {
                        let x = onRejected(self.reason);
                        resolvePromise(promise2,x,resolve,reject);
                    } catch (e) {
                        reject(e);
                    }
                },0)
            });
        }
    });
    return promise2
}
// 其中规范要求对回调中增加setTimeout处理

复制代码

可以看到resolve和reject都有一个处理新promise的方法resolvePromise,对其进行封装,达到处理不同情况的目的

/**
 * 对resolve 进行改造增强 针对resolve中不同值情况 进行处理
 * @param  {promise} promise2 promise1.then方法返回的新的promise对象
 * @param  {[type]} x         promise1中onFulfilled的返回值
 * @param  {[type]} resolve   promise2的resolve方法
 * @param  {[type]} reject    promise2的reject方法
 */
function resolvePromise(promise2,x,resolve,reject){
    if(promise2 === x){ // 如果从onFullfilled中返回的x就是promise2,就会导致循环引用报错
        return reject(new TypeError('Chaining cycle'));
    }
    let called; // 声明避免多次使用
    // x类型判断 如果是对象或者函数
    if(x!==null && (typeof x=== 'object' || typeof x === 'function')){
    // 判断是否是thenable对象
        try{
            let then = x.then; 
            if(typeof then === 'function'){
                then.call(x,y=>{ 
                    if(called) return; 
                    called = true;
                    resolvePromise(promise2,y,resolve,reject);
                },err=>{ 
                    if(called) return;
                    called = true;
                    reject(err);
                });
            }else{
            // 说明是普通对象/函数
                resolve(x);
            }
        }catch(e){
            if(called) return;
            called = true;
            reject(e);
        }
    }else{ 
        resolve(x);
    }
}
复制代码

以上基本实现了Promise的基本方法,根据Promise的用法,补充一些类上的方法

// 用于promise方法链时 捕获前面onFulfilled/onRejected抛出的异常
Promise.reject = function(reason){
    return new Promise((resolve,reject)=>{
        reject(reason);
    })
}
Promise.resolve = function(value){
    return new Promise((resolve,reject)=>{
        resolve(value);
    })
}
Promise.prototype.catch = function(onRejected){
    // 默认不写成功
    return this.then(null,onRejected);
};
/**
 * Promise.all Promise进行并行处理
 * 参数: promise对象组成的数组作为参数
 * 返回值: 返回一个Promise实例
 * 当这个数组里的所有promise对象全部变为resolve状态的时候,才会resolve。
 */
Promise.all = function(promises){
    return new Promise((resolve,reject)=>{
        let arr = [];
        let i = 0;
        function processData(index,data){
            arr[index] = data;
            if(++i == promises.length){
                resolve(arr);
            }
        }
        for(let i = 0;i<promises.length;i++){
            promises[i].then(data=>{ // data是成功的结果
                processData(i,data);
            },reject);
        }
    })
}
/**
 * Promise.race
 * 参数: 接收 promise对象组成的数组作为参数
 * 返回值: 返回一个Promise实例
 * 只要有一个promise对象进入 FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理(取决于哪一个更快)
 */
Promise.race = function(promises){
    return new Promise((resolve,reject)=>{
        for(let i = 0;i<promises.length;i++){
            promises[i].then(resolve,reject);
        }
    })
}

复制代码

最后我们导出方法

module.exports = Promise;
复制代码

至此一个符合PromiseA+规范的自己写的源码库完成了,可以测试使用这个库替代Promise,以测试是否有逻辑错误等,或者可以使用

npm install promises-aplus-tests -g
promises-aplus-test 文件名
复制代码

插件来测试该源码是否符合PromiseA+规范

希望这篇文章能帮到你,以上


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

PHP and MySQL Web Development

PHP and MySQL Web Development

Luke Welling、Laura Thomson / Sams / July 25, 2007 / $49.99

Book Description PHP and MySQL Web Development teaches you to develop dynamic, secure, commerical Web sites. Using the same accessible, popular teaching style of the three previous editions, this b......一起来看看 《PHP and MySQL Web Development》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

html转js在线工具
html转js在线工具

html转js在线工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换