浅谈Promise

111 阅读8分钟

什么是Promise?

Promise是抽象异步处理对象以及对其进行各种操作的组件。如果说到基于JavaScript的异步处理,我想大多数都会想到利用回调函数,node.js等规定在JavaScript的回调函数的第一个参数为Error对象,但这只是编码规范,并不具有强制性,即使采取不同的写法也不会出错,并且采取这种方式处理异步容易造成多重嵌套,引发回调地狱问题。Promise则是把类似的异步处理对象和处理规则进行规范化,在采取规定方法之外的写法都会出错。

let promise = getAsyncPromise("test.txt");
   promise.then(function(result){
       // 获取文件内容成功时的处理
   }).catch(function(error){
       // 获取文件内容失败时的处理
   });

除了promise对象规定的方法(then或catch)以外的方法都是不可以使用的。这样就可以将复杂的异步处理进行模式化,更加清晰明了,避免了回调地狱的问题和规范性的问题。

promise简介

constructor

Promise类似于XMLHttpRequest, 从构造函数Promise来创建一个新的promise对象作为接口。

const promise = new Promise(function (resolve, reject) {
// 异步处理
// 处理结束后调用resolve或reject
})
 promise.then(onFullfilled, onRejected)

resolve(成功时): onFullfilled会被调用

reject(失败时): onRejected会被调用

promise.then成功和失败时都可以使用,如果只想对异常进行处理可以采用promise.then(undefind, onRerjected)这种方式,不过这种情况下promise.catch(onRejected)应该是更好的选择。

除此之外,Promise这样的全局对象还拥有一些静态方法,如:Promise.allPromise.resolve()

promise的状态

用new Promise实例化的promise对象有三个状态。

resolve(成功时),会调用onFulfilled, reject(失败时),会调用onRejected, pending(初始状态)。

创建一个Promise对象

function getURL(url){
  return new Promise(function (resolve, reject){
        const req = new XMLHttpRequest()
        req.open('GET',     URL, true)
        req.onload = function () {
            if(req.status === 200){
                resolve(req.responseText)
            } else {
                reject(new Error(req.statusText))
            }
            req.send()
        }
    })
}

// 运行实例
const url = 'http://test.con/get'
getURL(url).then(function onFulfilled(value){
    console.log(value)
}).catch(function onRejected(error) {
    console.log(value)
})

getURL只有状态码为200的情况下才会调用resolve,其他情况则调用reject方法。

image.png

new Promise的快捷方式

静态方法Promise.resolve(value)可以认为是new Promise方法的快捷方式。

Promise.resolve(200)

// 等价于
new Promise(function(resolve){
    resolve(200)    
})

Promise.resolve(value)的返回值也是一个Promise对象,可以对其返回值进.then调用

Promise.resolve(42).then(function(value){
    console.log(value);
});

这种快捷方式在进行Promise对象初始化或者编写测试代码时都比较方便。

Thenable(类Promise对象)

Promise.resolve方法的另一个作用就是将thenable对象转换为Promise对象。就像我们将.length的非数组对象称为类数组对象一样,thenable指的是一个具有.then方法的对象。

如:jQuery.ajax()的返回值是thenable对象,这个对象具有.then方法。

将thenable对象转换为promise对象

var promise = Promise.resolve($.ajax('/json/comment.json'));// => promise对象
promise.then(function(value){
   console.log(value);
});

Promise.reject

Promise.reject是与promise.resolve类似的静态方法,不同的是,该方法调用的onRejected函数,并将错误对象传递给这个onRejected函数。

Promise只能进行异步操作?

var promise = new Promise(function (resolve){
    console.log("inner promise"); // 1
    resolve(42);
});
promise.then(function(value){
    console.log(value); // 3
});
console.log("outer promise"); // 2

// 执行顺序
inner promise // 1
outer promise // 2
42            // 3

事实上只有.then中的方法是异步进行的。

Promise链式调用

除了.then().catch()的这种链式调用的方法,其实Promise可以将任意个方法连在一起作为一个方法链。

aPromise.then(function taskA(value){
// task A
}).then(function taskB(vaue){
// task B
}).catch(function onRejected(error){
    console.log(error);
});

方法链的执行顺序

function taskA() {
    console.log("Task A");
}
function taskB() {
    console.log("Task B");
}
function onRejected(error) {
    console.log("Catch Error: A or B", error);
}
function finalTask() {
    console.log("Final Task");
}

var promise = Promise.resolve();
promise
    .then(taskA)
    .then(taskB)
    .catch(onRejected)
    .then(finalTask);

image.png

Promise方法链中如何传递参数

前面例子中Task都是相互独立的,只是简单被调用而已。如果TaskA想给TaskB传递一个参数该怎么办呢?

其实TaskA中return的返回值,会在TaskB执行时传给他。

function doubleUp(value) {
    return value * 2;
}
function increment(value) {
    return value + 1;
}
function output(value) {
    console.log(value);// => (1 + 1) * 2
}

const promise = Promise.resolve(1);
promise
    .then(increment)
    .then(doubleUp)
    .then(output)
    .catch(function(error){
        // promise chain中出现异常的时候会被调用
        console.error(error);
    });
  1. Promise.resolve(1); 传递 1 给 increment 函数
  2. 函数 increment 对接收的参数进行 +1 操作并返回(通过return
  3. 这时参数变为2,并再次传给 doubleUp 函数
  4. 最后在函数 output 中打印结果

image.png

每个方法return的值不仅只局限于字符串或数值类型,也可以是对象或Promise对象等复杂类型。returnd的值会由Promise.resolve进行相应的包装处理,因此不管返回的值是什么样的,最终.then的结果都是返回一个新创建的Promise对象。

Promise#catch

实际上Promise#catch只是Promise.then(undefined, onRejected)方法的一个别名而已。

var promise = Promise.reject(new Error("message"));
promise.catch(function (error) {
    console.error(error);
});

每次调用then都会返回一个新创建的promise对象

var aPromise = new Promise(function (resolve) {
    resolve(100);
});
var thenPromise = aPromise.then(function (value) {
    console.log(value);
});
var catchPromise = thenPromise.catch(function (error) {
    console.error(error);
});
console.log(aPromise !== thenPromise); // => true
console.log(thenPromise !== catchPromise);// => true

image.png

Promise.all

如果多个Promise对象都变为FuFilled状态的时候再进行下一步操作,该如何处理呢?

promise.all接收一个 promise对象的数组作为参数,当这个数组里的所有promise对象全部变为resolve或reject状态的时候,它才会去调用 .then 方法。

// `delay`毫秒后执行resolve
function timerPromisefy(delay) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve(delay);
        }, delay);
    });
}
var startDate = Date.now();
// 所有promise变为resolve后程序退出
Promise.all([
    timerPromisefy(1),
    timerPromisefy(32),
    timerPromisefy(64),
    timerPromisefy(128)
]).then(function (values) {
    console.log(Date.now() - startDate + 'ms');
    // 約128ms
    console.log(values);    // [1,32,64,128]
});

timerPromisefy 会每隔一定时间(通过参数指定)之后,返回一个promise对象,状态为FulFilled,其状态值为传给 timerPromisefy 的参数。

而传给 Promise.all 的则是由上述promise组成的数组。

var promises = [
    timerPromisefy(1),
    timerPromisefy(32),
    timerPromisefy(64),
    timerPromisefy(128)
];

这时候,每隔1, 32, 64, 128 ms都会有一个promise发生 resolve 行为。

也就是说,这个promise对象数组中所有promise都变为resolve状态的话,至少需要128ms。实际我们计算一下Promise.all 的执行时间的话,它确实是消耗了128ms的时间。

从上述结果可以看出,传递给Promise.all的promise并不是一个个的顺序执行的,而是同时开始、并行执行的。

Promise.race

接着我们来看看和Promise.all 类似的对多个promise对象进行处理的 Promise.race 方法。

它的使用方法和Promise.all一样,接收一个promise对象数组为参数。

Promise.all 在接收到的所有的对象promise都变为 FulFilled 或者 Rejected 状态之后才会继续进行后面的处理, 与之相对的是 Promise.race 只要有一个promise对象进入 FulFilled 或者 Rejected 状态的话,就会继续进行后面的处理。

像Promise.all时的例子一样,我们来看一个带计时器的 Promise.race 的使用例子。

// `delay`毫秒后执行resolve
function timerPromisefy(delay) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve(delay);
        }, delay);
    });
}
// 任何一个promise变为resolve或reject 的话程序就停止运行
Promise.race([
    timerPromisefy(1),
    timerPromisefy(32),
    timerPromisefy(64),
    timerPromisefy(128)
]).then(function (value) {
    console.log(value);    // => 1
});

上面的代码创建了4个promise对象,这些promise对象会分别在1ms,32ms,64ms和128ms后变为确定状态,即FulFilled,并且在第一个变为确定状态的1ms后, .then 注册的回调函数就会被调用,这时候确定状态的promise对象会调用 resolve(1) 因此传递给 value 的值也是1,控制台上会打印出1来。

手写一个Promise

const RESOLVED = 'RESOLVED'; // 成功
const REJECTED = 'REJECTED'; // 失败
const PENDING = 'PENDING'; // 等待态

const resolvePromise = (promise2, x, resolve, reject) => {
    // 1.循环引用 自己等待自己完成 错误的实现
    if (promise2 === x) { // 用一个类型错误 结束掉promise
        return reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
    }
    // 后续的条件要严格判断 保证代码能和别的库一起使用
    let called;
    if ((typeof x === 'object' && x != null) || typeof x === 'function') { // 有可能是一个promise
        // 要继续判断
        try {
            let then = x.then;
            if (typeof then === 'function') { // 只能认为是一个promise了
                // 不要写成x.then  直接then.call就可以了 因为x.then 会再次取值
                then.call(x, y => { // 根据promise的状态决定是成功还是失败
                    if (called) return;
                    called = true;
                    resolvePromise(promise2, y, resolve, reject); // 递归解析的过程
                }, e => {
                    if (called) return;
                    called = true;
                    reject(e); // 只要失败就失败
                });
            } else { // {then:'23'}
                resolve(x);
            }
        } catch (e) { // 防止失败了再次进入成功
            if (called) return;
            called = true;
            reject(e); // 取值出错
        }
    } else {
        resolve(x);
    }
}
class Promise {
    constructor(executor) {
        this.status = PENDING;
        this.value = undefined;
        this.reason = undefined;
        this.onResolvedCallbacks = []; //  专门用来存放成功的回调
        this.onRejectedCallbacks = []; // 专门用来存放失败的回调
        let resolve = (value) => { // 调用此方法就是成功

            if(value instanceof Promise){
                return value.then(resolve,reject); // 递归解析resolve中的参数,直到这个值是普通值
            }

            if (this.status === PENDING) {
                this.value = value;
                this.status = RESOLVED;
                this.onResolvedCallbacks.forEach(fn => fn());
            }
        }
        let reject = (reason) => {
            if (this.status === PENDING) {
                this.reason = reason;
                this.status = REJECTED;
                this.onRejectedCallbacks.forEach(fn => fn());
            }
        }
        try {
            executor(resolve, reject); // 立即执行
        } catch (e) { // 错误处理 需要直接走错误逻辑
            reject(e);
        }
    }

    // 1. promise 成功和失败的回调的返回值 可以传递到外层的下一个then
    // 2. 如果返回的是普通值的话 (传递到下一次的成功中,不是错误不是promise就是普通值) ,出错的情况(一定会走到下一次的失败),可能还要promise的情况(会采用promise的状态,决定走下一次的成功还是失败 )
    // 3.错误处理 如果离自己最近的then 没有错误处理(没有写错误函数) 会向下找
    // 4. 每次执行完promise.then方法后返回的都是一个“新的promise”
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
        onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
        let promise2 = new Promise((resolve, reject) => { // 为了实现链式调用
            if (this.status === RESOLVED) {
                setTimeout(() => {
                    try {
                        let x = onFulfilled(this.value);
                        // x可能是一个proimise
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                }, 0);
            }
            if (this.status === REJECTED) {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                }, 0);
            }
            if (this.status === PENDING) {
                this.onResolvedCallbacks.push(() => {
                    // todo...
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                });
                this.onRejectedCallbacks.push(() => {
                    // todo...
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                });
            }
        });
        return promise2;
    }
    catch(errCallback){
        return this.then(null,errCallback)
    }
    static resolve(data){
        return new Promise((resolve,reject)=>{
            resolve(data);
        })
    }
    static reject(reason){
        return new Promise((resolve,reject)=>{
            reject(reason);
        })
    }
}
// promise的延迟对象
Promise.defer = Promise.deferred = function () {
    let dfd = {};
    dfd.promise = new Promise((resolve,reject)=>{
        dfd.resolve = resolve;
        dfd.reject = reject;
    })
    return dfd
}
module.exports = Promise;