Zijue/blog

4.promise中的其它方法

Opened this issue · 0 comments

Zijue commented

延迟对象解决嵌套问题

在上一节中为了验证我们手写的promise是否符合规范,在手写的Promise类上添加了Promise.deferred方法。这个deferred是一种编程**,可以用于解决部分嵌套问题。比如下面这段代码:

function readFile(...args) {
    return new Promise((resolve, reject) => {
        fs.readFile(...args, (err, data) => {
            if (err) return reject(err);
            resolve(data);
        })
    })
}

使用deferred可以将代码改写为:

function readFile(...args) {
    let dfd = Promise.deferred();
    fs.readFile(...args, (err, data) => {
        if (err) return dfd.reject(err);
        dfd.resolve(data);
    });
    return dfd.promise
}

改完后的代码少了一层嵌套,好像也没什么用,但是这是一种**。

catchPromise.resolvePromise.reject

  • catch方法
    经常在写Promise时,需要捕获错误,但是then方法中需要传递两个参数;这时就可以用catch方法只捕获错误,同时后面可以继续then
readFile('./xxx.txt', 'uft8').then(data => {
    console.log('data');
}).catch(err => {
    console.log(err);
}).then(data=>{
    console.log('continue'); // 此处会继续执行
})

说白了,catch就是一个没有成功回调方法的then方法,实现代码如下:

class Promise {
    ...
    catch(errFn) {
        return this.then(null, errFn);
    }
    ...
}
  • Promise.resolve静态方法
    有时我们需要直接返回一个成功态的promise,这个时候我们就可以使用Promise.resolve;同时Promise.resolve还具备等待效果,如果我们传入的是一个promise,那么它会将该promise成功或失败的结果向下传递。先来实现传入普通值:
class Promise {
    ...
    static resolve(value) {
        return new Promise((resolve, reject) => {
            resolve(value);
        })
    }
    ...
}

假如我们传入一个Promise,那么结果会是这样:

Promise {
  status: 'FULFILLED',
  value: 'ok',
  reason: undefined,
  onFulfilledCallbacks: [],
  onRejectedCallbacks: []
}

我们写的代码出现这个的原因就是Promise中定义的resolve函数直接将传入的value赋给了promise实例,并将此值作为成功结果执行,因此需要对resolve函数进行修改:

class Promise {
    ...
        const resolve = (value) => {
            if (value instanceof Promise) { // 这个方法并不属于规范中的,只是为了和原生promise表现形式一样
                return value.then(resolve, reject);
            }
            if (this.status == PENDING) {
                this.value = value;
                this.status = FULFILLED;
                this.onFulfilledCallbacks.forEach(fn => fn());
            }
        };
    ...
}
  • Promise.resolve静态方法
    rejectresove实现原理一样,只是不具备等待功能,代码如下:
class Promise {
    ...
    static reject(err) {
        return new Promise((resolve, reject) => {
            reject(err);
        })
    }
    ...
}

Promise.allfinally

  • Promise.all:表示全部成功才成功,如果有一个失败则失败
    核心原理代码如下:
Promise.all = function (promises) {
    return new Promise((resolve, reject) => {
        let results = [];
        let index = 0;
        function process(v, k) { // 与after函数实现原理一致
            results[k] = v;
            if (++index == promises.length) { // 解决多个异步并发问题,只能靠计数器
                resolve(results);
            }
        }
        for (let i = 0; i < promises.length; i++) {
            let p = promises[i];
            if (p && typeof p.then == 'function') {
                p.then(data => { // 异步的
                    process(data, i);
                }, reject);
            } else {
                process(p, i); // 同步的
            }
        }
    })
}
  • finally:无论成功和失败都会执行的方法
    • finally如果返回的是一个promise,那么会有等待效果
    • 只有返回一个失败态的promise,才会将返回的promise失败的原因向下传递,否则传递finally之前的成功结果或失败原因

核心原理代码如下:

Promise.prototype.finally = function (cb) {
    return this.then((y) => {
        return Promise.resolve(cb()).then((d) => y);
    }, (r) => {
        // cb执行一旦报错 就直接跳过后续的then的逻辑,直接将错误向下传递
        return Promise.resolve(cb()).then(() => { throw r })
    })
}

如何将不是Promise的异步API转换成Promise

function promisify(fn) { // 高阶函数
    return function (...args) {
        return new Promise((resolve, reject) => {
            fn(...args, function (err, data) { // node 所有的api第一个参数都是error
                if (err) return reject(err);
                resolve(data);
            })
        })
    }
}

// 测试promisify方法
const fs = require('fs');
let read = promisify(fs.readFile);
read('z1.txt', 'utf8').then(data => {
    console.log(data)
});

promisify可以将所有的回调方法转化成promise,node的api可以使用.promises的方式引入promise的异步方法:

const fs = require('fs').promises;

Promise.race静态方法

race方法,调用的列表中任何一个成功或失败,就采用它的结果。实现原理如下:

Promise.race = function (promises) {
    return new Promise((resolve, reject) => {
        for (let promise of promises) {
            if (promise && typeof promise.then == 'function') {
                promise.then(resolve, reject)
            } else {
                resolve(promise);
            }
        }
    })
}
  • 使用race方法可以实现promise版的具有超时功能的图片懒加载,如下:
let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('图片加载完成');
    }, 3000);
});
let p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('请求超时');
    }, 2000);
})

Promise.race([p1, p2]).then(data => {
    console.log(data);
}, err => {
    console.log(err);
})

// 执行结果
$ 请求超时   -- 2s左右
                     -- 3s左右,程序才结束

promise是没法中断执行的,无论如何都会执行完毕,只是不采用这个promise的成功或失败的结果了。

  • 如何在不改变promise原有代码的前提下,提供一个abort中断方法
let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('图片加载完成');
    }, 3000);
});
function wrap(old) {
    let abort;
    let p2 = new Promise((resolve, reject) => {
        abort = reject; // 内置了一个promise,我们可以控制这个promise,来影响Promise.race的结果
    })
    let returnPromise = Promise.race([old, p2])
    returnPromise.abort = abort;
    return returnPromise
}
let newPromise = wrap(p1);

setTimeout(() => {
    newPromise.abort('超时 2000');
}, 2000);
newPromise.then(data => {
    console.log(data);
}, err => {
    console.log(err)
});

原理就是通过切片编程的**,返回一个新的promise,通过内置的promise影响Promise.race的结果

  • promise是没法中断执行的,但是可以中断链式调用:通过返回一个PENDING状态的Promise
Promise.resolve('1').then(data => {
    console.log(data);
    return new Promise(() => { }); // 返回一个promise,会采用他的状态;如果不成功也不失败,就不会向下执行了
}).then((data) => {
    console.log(data)
});
  • 小结:如果希望不采用原有的结果,可以通过Promise.race;如果希望中断promise的链式调用,则需要返回一个pending状态的promise实现。