promsie进阶

废话不多说,直接上干货。

1. promise 相关问题

(1)使用方式问题

then 里传两个回调函数
const promise = new Promise((resolve, reject) => {
  resolve(a);
});

promise.then(
  (res) => {
    console.log('resolve', res);
  }, 
  (err) => {
    console.log('reject', err.message);
  }
);
// reject a is not defined
then存在两个参数,第一个为成功的回调函数,第二个为错误的回调函数。
promise then的第二个回调函数等同于promise.catch()。
.then .catch 的方式
promise
  .then(() => {})
  .catch(() => {});
推荐使用 .then .catch 的方式。

(2)状态固化问题

promsie 使用 resolve 或 reject时,状态发生固化,意味着状态不再发生变化。

状态已经固化成成功状态时,catch并不能捕获到当前的异常。

let promise = new Promise((resolve, reject) => {
  resolve('ok');
  console.log(a);
});

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

// ok

(3)状态依赖问题

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(new Error('fail'))
  }, 3000);  
});

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(p1);
  }, 1000)
});

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

p1 作为参数传递给 p2 时,会导致 p2 状态无效。
p2 状态依赖 p1 状态,会等待p1状态完成后再固化状态,这是底层帮我们处理的事情。

(4)执行问题

const p1 = new Promise((resolve, reject) => {
  resolve(1);
  console.log('2');
});

p1.then(res => {
  console.log(res);
});

// '2'
// 1
resolve、reject 不会终止函数执行,只是改变 promise 函数的状态。

2. promise 的方法

(1)Promise.all()、Promise.race()

promise 通过 Promise.all 和 Promise.race 方法,来管理异步回调之间的关系。

Promise.all()

异步操作全部成功的时候返回成功,返回所有成功的promise的值。
如果全部都失败,返回第一个触发失败的promsie的信息。
如果有一个异步函数失败就失败,返回错误的promise的信息。

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000);
});

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(2);
  }, 3000);
});

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(3);
  }, 2000);
});

Promise.all([p1, p2, p3])
  .then(arr => {
    console.log(arr);
  })
  .catch(err => {
    console.log(err);
  });  

// [1, 2, 3]
Promise.race()

异步操作只有一个成功就成功,返回第一个执行成功的promise的值。
异步操作失败,返回失败的promise的值。

const p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000);
});

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(2);
  }, 3000);
});

const p3 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(3);
  }, 2000);
});

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

// 1

(2)Promise.resolve()

直接返回一个promise对象。
 let thenable = {
  then: function (resolve) {
    resolve(42);
  }
};

let p1 = Promise.resolve(thenable);

p1.then(val => console.log(val)); // 42

thenable: 绑定了 then 的对象都可以叫做thenable。

let promise = Promise.resolve('hello');

promise
  .then((val) => {
    console.log(val);
  });
// hello

(3)Promise.reject()

const promise = Promise.reject('123');

promise
  .then(res => {
    console.log('then', res);
  })
  .catch(err => {
    console.log('catch', err);
  });

3. promise 处理回调函数

根目录下存在 name.txt、number.txt、score.txt 文件,存放接下来需要读取的路径,如下图所示。

promise.png

score.txt 文件中,存放需要打印的信息。

function readFile (path) {
  return new Promise((resolve, reject) => {
    fs.readFile(path, 'utf-8', (err, data) => {
      if (data) {
        resolve(data);
        return;
      }
      console.log(err);
    });
  });
}

readFile('./name.txt')
  .then(data => readFile(data))
  .then(data => readFile(data))
  .then(data => console.log(data)); // 90分

我们可以通过 promise 包装回调函数,将异步回调函数 promise 化。

4. 自定义实现 promisify

当存在很多异步的回调函数时,每次 promise 化都很麻烦,为此可以定义一个函数。

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

接下来,使用就很简单啦。

const readFile = promisify(fs.readFile);

readFile('./name.txt', 'utf-8')
  .then(data => readFile(data, 'utf-8'))
  .then(data => readFile(data, 'utf-8'))
  .then(data => console.log(data)); // 90分

promisify 函数在 node 的8.0版本之后,已经被封装到 util 中,可以直接使用。

const fs = require('fs'),
      util = require('util');

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

readFile('./name.txt', 'utf-8')
  .then(data => readFile(data, 'utf-8'))
  .then(data => readFile(data, 'utf-8'))
  .then(data => console.log(data));

还有一个问题,假如想把 fs 模块的所有方法都 promise 化,应该怎么做?
在这里提供一种解决思路,使用 Object.entries 获取模块的所有方法遍历处理。

const fs = require('fs');

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

function promisifyAll (obj) {
  for (let [key, func] of Object.entries(obj)) {
    if (typeof func === 'function') {
      obj[key + 'Promise'] = promisify(func);
    }
  }
}

promisifyAll(fs);

fs.readFilePromise('./name.txt', 'utf-8')
  .then(data => fs.readFilePromise(data, 'utf-8'))
  .then(data => fs.readFilePromise(data, 'utf-8'))
  .then(data => console.log(data));

5. 总结

本篇文章对 promise 进行了补充讲解,希望可以帮助到大家。