Army_海军

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 :: 管理 ::

 Promise 是一个对象,它代表了一个异步操作的最终完成或者失败。

假设现在有一个名为 createAudioFileAsync() 的函数,如果给出一些配置和两个回调函数,这个函数能异步地生成音频文件。一个回调函数是文件成功创建时的回调,另一个则是出现异常时的回调。

以下为使用 createAudioFileAsync() 的示例:

// 成功的回调函数
function successCallback(result) {
  console.log("音频文件创建成功: " + result);
}

// 失败的回调函数
function failureCallback(error) {
  console.log("音频文件创建失败: " + error);
}

createAudioFileAsync(audioSettings, successCallback, failureCallback)

最新的方式就是返回一个 promise 对象,使得你可以将你的回调函数绑定在该 Promise 上:

如果函数 createAudioFileAsync() 被重写为返回 Promise 对象的形式,就可以像这样简单地使用:

const promise = createAudioFileAsync(audioSettings); 
promise.then(successCallback, failureCallback);

可简写为:

createAudioFileAsync(audioSettings).then(successCallback, failureCallback);

不同于“老式”的传入回调,在使用 Promise 时,会有以下约定:

因此,Promise 最直接的好处就是链式调用chaining)。

 

通过新的功能方法,我们把回调绑定到被返回的 Promise 上代替以往的做法,形成一个 Promise 链:

doSomething().then(function(result) {
  return doSomethingElse(result);
})
.then(function(newResult) {
  return doThirdThing(newResult);
})
.then(function(finalResult) {
  console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);

then里的参数是可选的,catch(failureCallback) 是 then(null, failureCallback) 的缩略形式。如下所示,我们也可以用箭头函数来表示:

doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
  console.log(`Got the final result: ${finalResult}`);
})
.catch(failureCallback);

注意:一定要有返回值,否则,callback 将无法获取上一个 Promise 的结果。(如果使用箭头函数,() => x 比 () => { return x; } 更简洁一些,但后一种保留 return 的写法才支持使用多个语句。)。

Catch 的后续链式操作

有可能会在一个回调失败之后继续使用链式操作,即 使用一个 catch,这对于在链式操作中抛出一个失败之后,再次进行新的操作很有用。请阅读下面的例子:

new Promise((resolve, reject) => {
    console.log('初始化');

    resolve();
})
.then(() => {
    throw new Error('有哪里不对了');
        
    console.log('执行「这个」”');
})
.catch(() => {
    console.log('执行「那个」');
})
.then(() => {
    console.log('执行「这个」,无论前面发生了什么');
});

输出结果如下:

初始化
执行“那个”
执行“这个”,无论前面发生了什么

注意:因为抛出了错误 有哪里不对了,所以前一个 执行「这个」 没有被输出。

错误传递

在之前的回调地狱示例中,你可能记得有 3 次 failureCallback 的调用,而在 Promise 链中只有尾部的一次调用。

doSomething()
.then(result => doSomethingElse(value))
.then(newResult => doThirdThing(newResult))
.then(finalResult => console.log(`Got the final result: ${finalResult}`))
.catch(failureCallback);

通常,一遇到异常抛出,Promise 链就会停下来,直接调用链式中的 catch 处理程序来继续当前执行。这看起来和以下的同步代码的执行很相似。

try {
  let result = syncDoSomething();
  let newResult = syncDoSomethingElse(result);
  let finalResult = syncDoThirdThing(newResult);
  console.log(`Got the final result: ${finalResult}`);
} catch(error) {
  failureCallback(error);
}

在 ECMAScript 2017 标准的 async/await 语法糖中,这种同步形式代码的对称性得到了极致的体现:

async function foo() {
  try {
    let result = await doSomething();
    let newResult = await doSomethingElse(result);
    let finalResult = await doThirdThing(newResult);
    console.log(`Got the final result: ${finalResult}`);
  } catch(error) {
    failureCallback(error);
  }
}

组合

Promise.resolve() 和 Promise.reject() 是手动创建一个已经 resolve 或者 reject 的 Promise 快捷方法。它们有时很有用。

Promise.all() 和 Promise.race() 是并行运行异步操作的两个组合式工具。

我们可以发起并行操作,然后等多个操作全部结束后进行下一步操作,如下:

Promise.all([func1(), func2(), func3()])
.then(([result1, result2, result3]) => { /* use result1, result2 and result3 */ });

可以使用一些聪明的 JavaScript 写法实现时序组合:

[func1, func2, func3].reduce((p, f) => p.then(f), Promise.resolve())
.then(result3 => { /* use result3 */ });

通常,我们递归调用一个由异步函数组成的数组时相当于一个 Promise 链:

Promise.resolve().then(func1).then(func2).then(func3);

我们也可以写成可复用的函数形式,这在函数式编程中极为普遍:

const applyAsync = (acc,val) => acc.then(val);
const composeAsync = (...funcs) => x => funcs.reduce(applyAsync, Promise.resolve(x));

composeAsync() 函数将会接受任意数量的函数作为其参数,并返回一个新的函数,该函数接受一个通过 composition pipeline 传入的初始值。这对我们来说非常有益,因为任一函数可以是异步或同步的,它们能被保证按顺序执行:

const transformData = composeAsync(func1, func2, func3);
const result3 = transformData(data);

在 ECMAScript 2017 标准中, 时序组合可以通过使用 async/await 而变得更简单:

let result;
for (const f of [func1, func2, func3]) {
  result = await f(result);
}
/* use last result (i.e. result3) */

 

 

posted on 2019-09-30 14:15  Army_海军  阅读(141)  评论(0)    收藏  举报