整理一些`Promise`库`bluebird`的常用api

  1. 整理一些Promise库bluebird的常用api
  2. Promis.promisify
  3. Promise.promisifyAll
  4. Cancellation
  5. Promise.spread
  6. Promise.bind
  7. Promise.props
  8. Promise.some

整理一些Promisebluebird的常用api

Promis.promisify

promisify可以帮我们吧一些常用异步方法或者库转化成Promise对象,这样我们就不用自己封装了;

const fs = require('fs');
const Promise = require('bluebird');

const readFile = Promise.promisify(fs.readFile);

readFile('./data/a.txt').then(data => console.log(data.toString()));

这里仅做个最简单的介绍,更多详细用法查看官网Promis.promisify

Promise.promisifyAll

const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));

fs.readFileAsync("./data/a.txt").then(contents => { // 实际是readFile方法,Async只是Promise.promisifyAll为我们自动添加的后缀,下面会介绍
    console.log(contents);
}).catch(err => {
    console.error(err);
});

可以为包装后的函数添加后缀,以区分原始方法,默认的后缀是Async

const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'), {suffix: 'Promise'});

fs.readFilePromise("./data/a.txt", "utf8").then(contents => {
    console.log(contents);
}).catch(err => {
    console.error(err);
});

更多用法查看官网Promis.promisify

Promise.promisifyAll可以接受数组,一次性promisify多个类;

var Pool = require("mysql/lib/Pool");
var Connection = require("mysql/lib/Connection");
Promise.promisifyAll([Pool, Connection]);

Cancellation

javascript中的Promise是不支持取消异步操作的,但是bluebird支持取消异步操作,这个是一个很好的特性,能够帮我们解决很多问题,比如在React中,组件挂载时请求接口,如果在接口返回之前这个组件被卸载了,React会抛出一个异常,我们就可以利用bluebird的这一特性,来帮我们处理这个问题;

想要使用Cancellation必须配置开启,如下第二行代码,此时Promise就可以取消了,还可以定义取消的回调函数,当取消的时候,执行一些操作。

注意:.cancel操作是同步的

const Promise = require('bluebird');
Promise.config({cancellation: true});

let p1 = new Promise(resolve => {
    setTimeout(() => resolve('p1'), 3000);
})

// 第三个参数只有当cancellation配置为true时才有
let p2 = new Promise((resolve, reject, onCancel) => {
    setTimeout(() => resolve('p2'), 5000);
    onCancel(() => console.log('p2 canceled'))
})

let p3 = new Promise(resolve => {
    setTimeout(() => resolve('p3'), 7000);
})

p1.then(res => console.log(res));
p2.then(res => console.log(res));
p3.then(res => console.log(res));
console.log('-----------------');
p2.cancel();

输出

----------------- // 执行栈直接输出
p2 canceled       // 调用p2的cancel方法时输出,.cancel方法是同步的,因此会在-----后直接输出
p1                // 等待3秒后输出
p3                // 等待7秒后输出,注意这里不是p1三秒结束后再等待7秒,而是从一开始等待7秒,因为Promise一旦创建就立即执行了

使用已经取消了的Promise是会报错的SubError [CancellationError]: late cancellation observer

const Promise = require('bluebird');
Promise.config({cancellation: true});

let p2 = new Promise((resolve, reject, onCancel) => {
    setTimeout(() => resolve('p2'), 5000);
    onCancel(() => console.log('p2 canceled'));
})

p2.cancel(); // 已取消

p2.then(res => {
    console.log(res);
}).catch(err => console.log(err));

结果

p2 canceled
SubError [CancellationError]: late cancellation observer
    at Promise._then (C:\Users\lyucan\Desktop\pro\newRepo\Repositories\node_modules\bluebird\js\release\promise.js:285:21)
    at Promise.then (C:\Users\lyucan\Desktop\pro\newRepo\Repositories\node_modules\bluebird\js\release\promise.js:154:17)
    at Object.<anonymous> (C:\Users\lyucan\Desktop\pro\newRepo\Repositories\myrepo
    ...

Promise.spread

Promise.spread可以把一个Promise数组进行解构,例如我们使用Promise.all进行多个异步操作,正常的.then拿到的入参就是一个数组,如果我们使用Promise.spread,那么我们获取到的就是数组里对应的值,而不是一个数组,结果和Promise对象的数组顺序一一对应,如果只传一个入参,则只获取第一个结果。

const fs = require('fs');
const Promise = require('bluebird');
const readFile = Promise.promisify(fs.readFile);

Promise.all([
    readFile('./data/a.txt'),
    readFile('./data/b.txt')
]).then(res => console.log(res.map(str => str.toString())))

// [ 'aaaaaaaaaaaaaaaaaaaaaa', 'bbbbbbbbbbbbbbbbbbbbbb' ]

使用Promise.spread,我们可以看到入参是数组每一项的值,而不是一整个数组

const fs = require('fs');
const Promise = require('bluebird');
const readFile = Promise.promisify(fs.readFile);

Promise.all([
    readFile('./data/a.txt'),
    readFile('./data/b.txt')
]).spread((res1, res2) => console.log(res1.toString(), res2.toString()))

// 'aaaaaaaaaaaaaaaaaaaaaa' 'bbbbbbbbbbbbbbbbbbbbbb'

Promise.bind

Promise.bind可以修改当前的this指向

const fs = require('fs');
const Promise = require('bluebird');
const readFile = Promise.promisify(fs.readFile);

class MyClass {
    constructor(){
        this.fileName = './data/a.txt';
    }
    error(error) {
        console.log(error)
    }
}
MyClass.prototype.method = function() {
    console.log(this); // 这里this指向MyClass
    return readFile(this.fileName).bind(this) // 绑定this指向
    .then((contents) => {
        console.log(contents)
        console.log(this)   // 这里this指向MyClass,如果没有上面的bind(this),那么这里将指向全局对象
    }).catch((e) => {
        this.error(e.stack); // 这里this指向MyClass,,如果没有上面的bind(this),那么这里将指向全局对象
    });
};

let me = new MyClass()
me.method()

Promise.props

Promise.propsPromise.all的作用类似,都是组合Promise对象,最大的区别是,Promise.all是需要一个具有iterator接口的对象,例如数组,而Promise.props的入参是一个key: [Promise]对象,而结果也会是一个key: [result]对象,

const fs = require('fs');
const Promise = require('bluebird');
const readFile = Promise.promisify(fs.readFile);

Promise.props({
    txtA: readFile('./data/a.txt'),
    txtB: readFile('./data/b.txt'),
}).then(result => {
    console.log(result.txtA.toString());
    console.log(result.txtB.toString());
})

// aaaaaaaaaaaaaaaaaaaaaa
// bbbbbbbbbbbbbbbbbbbbbb

Promise.some

Promise.some接收两个参数,第一个是Promise对象数组(不一定是数组,具有iterator接口就行),第二个参数是返回的个数,这个个数是在所有的Promise对象中,状态最快变为fulfilled的前几个,例如下面的代码,读取4个文件,返回最快读到的前2个;

const fs = require('fs');
const Promise = require('bluebird');
const readFile = Promise.promisify(fs.readFile);

Promise.some([
    readFile('./data/a.txt'),
    readFile('./data/b.txt'),
    readFile('./data/c.txt'),
    readFile('./data/d.txt'),
], 2).then(res => {console.log(res)})

如果成功的个数小于第二个参数指定的个数,会抛出一个异常

Unhandled rejection AggregateError: aggregate error
    at SomePromiseArray._checkOutcome (C:\Users\lyucan\Desktop\pro\newRepo\Repositories\node_modules\bluebird\js\release\some.js:82:17)
    at SomePromiseArray._promiseRejected (C:\Users\lyucan\Desktop\pro\newRepo\Repositories\node_modules\bluebird\js\release\some.js:69:17)
    at Promise._settlePromise (C:\Users\lyucan\Desktop\pro\newRepo\Repositories\node_modules\bluebird\js\release\promise.js:611:26)

我们可以直接.catch捕获,但是此时捕获的错误是一个rejected Promise的数组,具有length属性和一些数组的操作方法

const fs = require('fs');
const Promise = require('bluebird');
const readFile = Promise.promisify(fs.readFile);

Promise.some([
    readFile('./data/a.txt'),
    readFile('./data/bd.txt'),
    readFile('./data/cd.txt'),
], 2).then(res => {console.log(res)}).catch(err => console.log(err))

此时bd.txtcd.txt文件是不存在的,只有a.txt能读取成功,而我们定义了成功的数量是前两个,此时就会进入异常

SubError [AggregateError]: aggregate error
    at SomePromiseArray._checkOutcome (C:\Users\lyucan\Desktop\pro\newRepo\Repositories\node_modules\bluebird\js\release\some.js:82:17)
    ...
    at processImmediate (internal/timers.js:439:21) {
  '0': [OperationalError: ENOENT: no such file or directory, open 'C:\Users\lyucan\Desktop\pro\newRepo\Repositories\myrepo\nodejs\promise\data\bd.txt'] {
    cause: [Error: ENOENT: no such file or directory, open 'C:\Users\lyucan\Desktop\pro\newRepo\Repositories\myrepo\nodejs\promise\data\bd.txt'] {
      errno: -4058,
      code: 'ENOENT',
      syscall: 'open',
      path: 'C:\\Users\\lyucan\\Desktop\\pro\\newRepo\\Repositories\\myrepo\\nodejs\\promise\\data\\bd.txt'
    },
    isOperational: true,
    errno: -4058,
    code: 'ENOENT',
    syscall: 'open',
    path: 'C:\\Users\\lyucan\\Desktop\\pro\\newRepo\\Repositories\\myrepo\\nodejs\\promise\\data\\bd.txt'
  },
  '1': [OperationalError: ENOENT: no such file or directory, open 'C:\Users\lyucan\Desktop\pro\newRepo\Repositories\myrepo\nodejs\promise\data\cd.txt'] {
    cause: [Error: ENOENT: no such file or directory, open 'C:\Users\lyucan\Desktop\pro\newRepo\Repositories\myrepo\nodejs\promise\data\cd.txt'] {
      errno: -4058,
      code: 'ENOENT',
      syscall: 'open',
      path: 'C:\\Users\\lyucan\\Desktop\\pro\\newRepo\\Repositories\\myrepo\\nodejs\\promise\\data\\cd.txt'
    },
    isOperational: true,
    errno: -4058,
    code: 'ENOENT',
    syscall: 'open',
    path: 'C:\\Users\\lyucan\\Desktop\\pro\\newRepo\\Repositories\\myrepo\\nodejs\\promise\\data\\cd.txt'
  },
  length: 2
}

返回的整个错误是可以调用数组方法的,例如

const fs = require('fs');
const Promise = require('bluebird');
const readFile = Promise.promisify(fs.readFile);

Promise.some([
    readFile('./data/a.txt'),
    readFile('./data/bd.txt'),
    readFile('./data/cd.txt'),
], 2).then(res => {console.log(res)}).catch(err => console.log(err.map(errItem => errItem)))

结果

[
  [OperationalError: ENOENT: no such file or directory, open 'C:\Users\lyucan\Desktop\pro\newRepo\Repositories\myrepo\nodejs\promise\data\bd.txt'] {
    cause: [Error: ENOENT: no such file or directory, open 'C:\Users\lyucan\Desktop\pro\newRepo\Repositories\myrepo\nodejs\promise\data\bd.txt'] {
      errno: -4058,
      code: 'ENOENT',
      syscall: 'open',
      path: 'C:\\Users\\lyucan\\Desktop\\pro\\newRepo\\Repositories\\myrepo\\nodejs\\promise\\data\\bd.txt'
    },
    isOperational: true,
    errno: -4058,
    code: 'ENOENT',
    syscall: 'open',
    path: 'C:\\Users\\lyucan\\Desktop\\pro\\newRepo\\Repositories\\myrepo\\nodejs\\promise\\data\\bd.txt'
  },
  [OperationalError: ENOENT: no such file or directory, open 'C:\Users\lyucan\Desktop\pro\newRepo\Repositories\myrepo\nodejs\promise\data\cd.txt'] {
    cause: [Error: ENOENT: no such file or directory, open 'C:\Users\lyucan\Desktop\pro\newRepo\Repositories\myrepo\nodejs\promise\data\cd.txt'] {
      errno: -4058,
      code: 'ENOENT',
      syscall: 'open',
      path: 'C:\\Users\\lyucan\\Desktop\\pro\\newRepo\\Repositories\\myrepo\\nodejs\\promise\\data\\cd.txt'
    },
    isOperational: true,
    errno: -4058,
    code: 'ENOENT',
    syscall: 'open',
    path: 'C:\\Users\\lyucan\\Desktop\\pro\\newRepo\\Repositories\\myrepo\\nodejs\\promise\\data\\cd.txt'
  }
]

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 289211569@qq.com