
全栈工程师,编程爱好者
['prɒmɪs]
n. 许诺,允诺;希望
The Promise object is used for asynchronous computations.
A Promise represents a value which may be available now, or in the future, or never.
--MDN
Promise 对象用于异步计算。
一个 Promise 表示一个现在、将来或永不可能可用的值。
--MDN 中文
 
 
假设你去到一家饭店,自己找座坐下了,然后招呼服务员拿菜单来。
服务员说:"对不起,我是'同步'服务员,我要服务完这张桌子才能招呼你。"
你是不是很想抽ta?
那一桌人明明已经吃上了,你只是想要菜单,这么小的一个动作,服务员却要你等到别人的一个大动作完成。
这就是"同步"的问题:
顺序交付的工作1234,必须按照1234的顺序完成。
异步,则是将耗时很长的 A 交付的工作交给系统之后,就去继续做 B 交付的工作。等到系统完成之后,再通过回调或者事件,继续做 A 剩下的工作。
从观察者的角度,看起来 AB 工作的完成顺序,和交付他们的时间顺序无关,所以叫"异步"。
对异步的依赖进一步加剧了……
稍有不慎,就会踏入“回调地狱”。

假设需求:
遍历目录,找出最大的一个文件。
function findLargest(dir, callback) {
  fs.readdir(dir, function (err, files) {
    if (err) return callback(err);
    let count = files.length; // [1]
    let errored = false;
    let stats = [];
    files.forEach( file => {
      fs.stat(path.join(dir, file), (err, stat) => {
        if (errored) return; // [2]
        if (err) {
          errored = true;
          return callback(err);
        }
        stats.push(stat); // [3]
        if (--count === 0) { // [4]
          let largest = stats
            .filter(function (stat) { return stat.isFile(); })
            .reduce(function (prev, next) {
              if (prev.size > next.size) return prev;
              return next;
            });
          callback(null, files[stats.indexOf(largest)]);
        }
      });      
    });    
  });
}
findLargest('./path/to/dir', function (err, filename) {
  if (err) return console.error(err);
  console.log('largest file was:', filename);
});
回调有四个问题:
return 和 throw有没有一种方案,既能保留异步在无阻塞上的优势,又能让我们写代码写的更舒服呢?
社区经过长时间探索,最终总结出:
new Promise(
  /* 执行器 executor */
  function (resolve, reject) {
    // 一段耗时很长的异步操作
    resolve(); // 数据处理完成
    reject(); // 数据处理出错
  }
)
  .then(function A() { 
    // 成功,下一步
  }, function B() {
    // 失败,做相应处理
  });
pending [待定] 初始状态fulfilled [实现] 操作成功rejected [被否决] 操作失败.then() 执行后续步骤。接下来,看一个简单的范例
./sample/timeout.js
再看一个
./sample/timeout2.js
假如一个 Promise 已经完成了,再 .then() 会怎样?
./sample/fulfilled-then.js
假如在 .then() 的函数里面不返回新的 Promise,会怎样?
./sample/timeout3.js
.then().then() 接受两个函数作为参数,分别代表 fulfilled 和 rejected.then() 返回一个新的 Promise 实例,所以它可以链式调用.then() 根据其最终状态,选择特定的状态响应函数执行.then() 会在新 Promise 状态改变之后执行.then().then() 里有 .then() 的情况因为 .then() 返回的还是 Promise 实例。
会等里面的 .then() 执行完,在执行外面的。
对于我们来说,此时最好将其展开,会更好读。
./sample/nested-then.js
问题:下面的四种 promises 的区别是什么
// #1
doSomething().then(function () {
  return doSomethingElse();
});
// #2
doSomething().then(function () {
  doSomethingElse();
});
// #3
doSomething().then(doSomethingElse());
// #4
doSomething().then(doSomethingElse);
doSomething()
  .then(function () {
    return doSomethingElse();
  })
  .then(finalHandler);
答案:
doSomething
|-----------|
            doSomethingElse(undefined)
            |------------|
                         finalHandler(resultOfDoSomethingElse)
                         |------------|
doSomething()
  .then(function () {
    doSomethingElse();
  })
  .then(finalHandler);
答案:
doSomething
|-----------------|
                  doSomethingElse(undefined)
                  |------------------|
                  finalHandler(undefined)
                  |------------------|
doSomething()
  .then(doSomethingElse())
  .then(finalHandler);
答案:
doSomething
|-----------------|
doSomethingElse(undefined)
|---------------------------------|
                  finalHandler(resultOfDoSomething)
                  |------------------|
doSomething()
  .then(doSomethingElse)
  .then(finalHandler);
答案:
doSomething
|-----------|
            doSomethingElse(resultOfDoSomething)
            |------------|
                         finalHandler(resultOfDoSomethingElse)
                         |------------------|
注:以上4道题及答案均来自
 We have a problem with promises
Promise 会自动捕获内部异常,并交给 rejected 响应函数处理。
./sample/error.js
./sample/error-reject.js
通常有两种做法:
reject('错误信息').then(null, message => {})throw new Error('错误信息').catch( message => {})我推荐使用第二种,更加清晰,更加好读,并且可以捕获前面的错误。
来看一个稍微复杂一些的,捕获错误的范例:
.catch() + .then()./sample/catch-then.js
注意:强烈建议在所有队列最后都加上
.catch(),以避免漏掉错误处理造成意想不到的问题。
doSomething()
  .doAnotherThing()
  .doMoreThing()
  .catch( err => {
    console.log(err);
  });
Promise.all()
Promise.all([p1, p2, p3, ....]) 用于将多个 Promise 实例,包装成一个新的 Promise 实例。
./sample/all.js
Promise.all() 最常见就是和 .map() 连用。
我们改造一下前面的例子。
./sample/map.js
有时候我们不希望所有动作一起发生,而是按照一定顺序,逐个进行。
let promise = doSomething();
promise = promise.then(doSomethingElse);
promise = promise.then(doSomethingElse2);
promise = promise.then(doSomethingElse3);
....
.forEach()function queue(things) {
  let promise = Promise.resolve();
  things.forEach( thing => {
    promise = promise.then( () => {
      return new Promise( resolve => {
        doThing(thing, () => {
          resolve();
        });
      });
    });
  });
  return promise;
}
queue(['lots', 'of', 'things', ....]);
.reduce()function queue(things) {
  return things.reduce( (promise, thing) => {
    return promise.then( () => {
      return new Promise( resolve => {
        doThing(thing, () => {
          resolve();
        });
      });
    });
  }, Promise.resolve());
}
queue(['lots', 'of', 'things', ....]);
两个常见错误:
....
  things.forEach( thing => {
    promise.then( () => {
      return new Promise( resolve => {
        doThing(thing, () => {
          resolve();
        });
      });
    });
  });
....
没有把 .then() 产生的新 Promise 实例赋给 promise,没有生成队列。
function queue(things) {
  return things.reduce( (promise, thing) => {
    let step = new Promise( resolve => {
      doThing(thing, () => {
        resolve();
      });
    });
    return promise.then( step );
  }, Promise.resolve());
}
Promise 实例创建之后,会立刻运行执行器代码,所以这个也无法达成队列的效果。
假设需求:
开发一个爬虫,抓取某网站。
let url = ['http://blog.meathill.com/'];
function fetchAll(urls) {
  return urls.reduce((promise, url) => {
    return promise.then( () => {
      return fetch(url);
    });
  }, Promise.resolve());
}
function fetch(url) {
  return spider.fetch(url)
    .then( content => {
      return saveOrOther(content);
    })
    .then( content => {
      let links = spider.findLinks(content);
      return fetchAll(links);
    });
}
fetchAll(url);
关于 Generator 的详情,请参阅相关文档。
简而言之,
Generator 可以在执行中中断,并等待唤起。
let generator = function* (urls) {
  let loaded = [];
  while (urls.length > 0) {
    let url = urls.unshift();
    yield spider.fetch(url)
      .then( content => {
        loaded.push(url);
        return saveOrOther(content);
      })
      .then( content => {
        let links = spider.findLinks(content);
        links = _.without(links, loaded);
        urls = urls.concat(links);
      });
  }
  return 'over';
};
function fetch(urls) {
  let iterator = generator();
  function next() {
    let result = iterator.next();
    if (result.done) {
      return result.value;
    }
    let promise = iterator.next().value;
    promise.then(next);
  }
  next();
}
let urls = ['http://blog.meathill.com'];
fetch(urls);
Promise.resolve()fulfilled 的 Promise 实例fulfuilled 响应函数会得到这个参数thenable,立刻执行它的 .then()./sample/resolve.js
Promise.reject()Promise.reject() 会返回一个状态为 rejected 的 Promise 实例。
Promise.reject() 不认 thenable
./sample/reject.js
Promise.race()Promise.race() 功能类似 Promise.all(),不过它是有一个完成就算完成。
./sample/race.js
把回调包装成 Promise 是最常见的应用。
它有两个显而易见的好处:
./sample/wrap.js
假设需求:用户点击按钮,弹出确认窗体,用户确认和取消有不同的处理。
样式问题,不能使用 window.confirm()。
// 弹出窗体
let confirm = popupManager.confirm('您确定么?');
confirm.promise
  .then(() => {
    // do confirm staff
  })
  .catch(() => {
    // do cancel staff
  });
// 窗体的构造函数
class Confirm {
  constructor() {
    this.promise = new Promise( (resolve, reject) => {
      this.confirmButton.onClick = resolve;
      this.cancelBUtton.onClick = reject;
    });
  }
}
jQuery 已经实现了 Promise。参见 jqXHR
$.ajax(url, {
  dataType: 'json'
})
  .then(json => {
    // 该干啥干啥
  });
如果你需要在 IE 中使用 Promise,有两个选择:
Fetch API 是 XMLHttpRequest 的现代化替代方案,它更强大,也更友好。
它直接返回一个 Promise 实例。
fetch('some.json')
  .then( response => {
    return response.json();
  })
  .then( json => {
    // do something with the json
  })
  .catch( err => {
    console.log(err);
  });
放手用吧,少年!
相对于传统的回调模式,Promise 有着巨大的进步,值得我们学习和使用。
return/try/catch 的问题ES2017 新增运算符,增加新的语言元素,赋予 JavaScript 以顺序手法编写异步脚本的能力。
return/try/catch。function resolveAfter2Seconds(x) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(x);
    }, 2000);
  });
}
async function f1() {
  var x = await resolveAfter2Seconds(10);
  console.log(x); // 10
}
f1();
具体的内容请参考 MDN async 文档 和 await 文档
这是我犯过的一些错误,希望成为大家前车之鉴。
.resolve() .reject() 不会自动 return。.resolve() .reject() throw err 才会改变状态,.then() 不需要。.resolve() 只会返回一个值,返回多个值请用数组或对象。