Promise与异步编程
Promise 是 es6 引入的异步处理方案,让我们可以采用链式的写法注册回调函数,摆脱多层异步回调函数嵌套的情况,使代码更加简洁。
基本用法
用new Promise() 创建一个 Promise 对象:
let p = new Promise((resolve, reject) => {
  //
})
Promise 对象有3种状态:
pending: 表示进行中的状态fulfilled: 任务完成的状态,调用resolve()后触发rejected: 任务失败的状态, 调用rejected()后触发
状态只能从 pending到 fulfilled,或者 peding 到 rejected。并且状态一旦发生改变,将不会恢复。
Promise对象的 then() 方法,第一个参数为状态变成fulfilled的处理函数,第二个为 rejected 的处理函数。处理函数的参数通过 resolve()方法或者 reject()方法的参数传递:
let p = new Promise((resolve, reject) => {
  // 修改promise对象的状态为fulfilled
  resolve(1);
});
p.then(v => {
  console.log(v);   // 1
});
catch() 方法同样可以捕获失败状态的 Promise 对象,所以下面两种写法等价:
let p = new Promise((resolve, reject) => {
  // 修改promise对象的状态为rejected
  reject(new Error('boom'));
});
p.then(null, err => {
  console.log(err.message);  // boom
});
// 等价于
p.catch(err => {
  console.log(err.message); // boom
});
在Promise 初始化函数中抛出错误也是变成 rejected 状态:
let p = new Promise((resolve, reject) => {
  throw new Error('boom');
});
p.catch(err => {
  console.log(err.message); // boom
});
对于未处理的错误,catch() 总是能捕捉到。比如上面可以改写成:
let p = new Promise((resolve, reject) => {
  throw new Error('boom');
});
p.then(null).catch(err => {
  console.log(err.message); // boom
});
立即完成的Promise
Promise.resolve() 方法只接收一个参数并返回一个完成态的 Promise:
let p = Promise.resolve(1);
p.then(v => console.log(v)); // 1
// 等价于
let p = new Promise((resolve, reject) => {
  resolve(1)
})
如果方法传入的是一个非Promise的 thenable 对象,指的是拥有 then()方法并接收 resolve 和 reject 两个参数的普通对象。结果会返回一个新的Promise,并且执行thenable 中的then方法:
let thenable = {
  then(resolve, reject) {
    resolve(44);
  }
};
let p = Promise.resolve(thenable);
p.then(v => console.log(v)); // 44
如果传入的是一个Promise 对象,会原封不动的返回这个对象。
另外,通过 Promise.resolve() 创建的对象的 then() 方法执行是在本次事件循环:
setTimeout(() => {
  console.log('next event loop');
}, 0);
let p = Promise.resolve('current event loop');
p.then(v => console.log(v));
// current event loop
// next event loop
利用 Promise.reject()可以创建立即失败的Promise,参数用法和上面类似。
串行的Promise
其实每次调用then() 和 catch() 方法都会返回一个Promise对象,因此我们可以链式的调用:
let p = Promise.resolve(42);
p.then(v => console.log(v)).then(() => console.log('finish'));  // 42 finish
在 then() 或者 catch() 方法中抛出错误,会被下一个catch()方法捕获。所以我们推荐在链式的Promise最后一个为 catch():
let p = Promise.resolve(42);
p.then(v => {
  console.log(v);   // 42
  throw new Error('boom');
}).catch(e => {
  console.log(e.message);  // boom
});
在then() 方法中可以return一个值,相当于该值会先作为 Promise.resolve() 参数调用,然后返回一个 Promise 对象,后续的方法调用取决与这个 Promise 的状态:
let p = Promise.resolve(42);
p.then(v => {
  console.log(v);
  return v + 1; // 相当于 Promise.resolve(v+1);
})
  .then(v => {
    console.log(v);
  })
// 42
// 43
返回一个 thenable 对象:
let p = Promise.resolve(42);
let thenable = {
  then(resolve, reject) {
    reject(44);
  }
};
p.then(v => {
  console.log(v);
  return thenable; // 相当于 Promise.resolve(thenable);
})
  .then(v => {
    console.log(v);
  })
  .catch(v => {
    console.log('error: ' + v);
  });
// 42
// error: 44
响应多个Promise
Promise.all() 方法接收一个参数并返回一个 Promise,该参数是含有多个Promise 对象的可迭代元素,例如数组。当每个 Promise对象的状态都为fulfilled 时,返回的 Promise 状态才是 fulfilled:
let p1 = Promise.resolve(1);
let p2 = new Promise((resolve, reject) => {
  resolve(2);
});
let p3 = new Promise((resolve, reject) => {
  resolve(3);
});
let p = Promise.all([p1, p2, p3]);
p.then(v => console.log(v));  // [ 1, 2, 3 ]
只要其中有一个Promise状态为 rejected,最后的返回 Promise的状态就为 rejected:
let p1 = Promise.resolve(1);
let p2 = new Promise((resolve, reject) => {
  reject(new Error('boom'));
});
let p3 = new Promise((resolve, reject) => {
  resolve(3);
});
let p = Promise.all([p1, p2, p3]);
p.then(v => console.log(v)).catch(e => console.log(e.message)); // 'boom'
Promise.race()方法接收的参数和 all()一样,不同的是,传给 race()方法的 Promise 对象只要有一个状态发生改变,返回的 Promise 的状态就会改变,无需等其他的 Promise 状态都改变:
let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 0);
});
let p2 = Promise.resolve(2);
let p3 = new Promise((resolve, reject) => {
  reject(3);
});
let p = Promise.race([p1, p2, p3]);
p.then(v => console.log(v)).catch(e => console.log(e.message)); // 2
异步处理应用
在之前我们有一个模拟的异步任务:
function fetchData(url, cb) {
  setTimeout(() => {
    cb({ code: 0, data: url });
  }, 1000);
}
fetchData('aa.com', res => console.log(res.data));  // aa.com
我们可以利用 Promise 的方式改写这个异步任务:
function fetchData(url) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({ code: 0, data: url });
    }, 1000);
  });
}
fetchData('aa.com').then(res => console.log(res.data)); // aa.com
对于自动执行函数run()我们可以改写成支持 Promise 异步的形式:
function run(gen) {
  let g = gen();
  function next(data) {
    let result = g.next(data);
    if (result.done) return;
    let p = Promise.resolve(result.value);
    p.then(value => {
      next(value);
    }).catch(err => {
      g.throw(err);
    });
  }
  next();
}
FE-Note