Promise

为什么要有Promise?

Promise是异步编程的一种解决方案。有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。Promise对象提供了统一的接口,使得控制异步操作更加容易。

目录结构:

一、Promise的特点

二、基本用法

三、Promise的方法

四、

一、Promise的特点

1、对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。

2、一旦状态改变,就不会再变,任何时候都可以得到这个结果。如果改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

二、基本用法

const promise = new Promise(function(resolve, reject){
    // ... some code

    if(/*异步操作成功*/){
        resolve(value);
    }else{
        reject(error);
    }
})

Promise的构造函数参数是一个函数,该函数的两个参数分别是resolvereject,它们是两个函数,由JavaScript提供,不用自己部署。

resolve函数的作用:将Promise对象的状态由pending变为resolved,在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态由pending变为rejected,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去,这个参数是可选的。

promise.then(function(value){
    //success
    }, function(error){ // 这个函数是可选的,不一定要提供
    //failure
})

使用Promise的栗子:Promise包装了一个图片加载的异步操作。如果加载成功,就调用resolve方法,否则就调用reject方法。

function loadImageAsync(url) {
    return new Promise(function(resolve, reject){
        const image = new Image();

        image.onload = function(){
            resolve(image);
        };

        image.onerror = function(){
            reject(new Error('Could not load image at  '+url)
        }

        image.src = url
    })
}

用Promise对象实现Ajax操作

const getJSON = function (url) {
    const promise = new Promise(function (resolve, reject) {
        const handler = function () {
            if (this.readyState !== 4) {
                return;
            }
            if (this.status === 200) {
                resolve(this.response)
            } else {
                reject(new Error(this.statusText))
            }
        }
        const xhr = new XMLHttpRequest();
        xhr.open('GET', url);
        xhr.onreadystatechange = handler;
        xhr.responseType = 'json';
        xhr.setRequestHeader('Accept', 'application/json');
        xhr.send();
    });
    return promise;
}

getJSON('/posts.json').then(function (json) {
    console.log('Contents:' + json)
}, function (error) {
    console.log('出错了', error)
})

getJSON内部,resolve函数和reject函数调用时,都带有参数。reject函数的参数通常是Error对象的实例,表示抛出的错误;resolve函数的参数除了正常的值以外,还可能是另一个Promise实例。

下图一、promise内部调用reject函数之后,在then方法内第二个参数函数里面输出抛出的错误。

下图二、(同上)虽然在最后有catch语句,但是不会被调用。

下图三、promise内部调用reject函数之后,没有then方法的第二个参数,那么会在catch方法里面输出抛出的错误。

var promise = new Promise(function(resolve, reject){
    reject(new Error('123'))
}).then(function(data){
    console.log(data)
},function(error){
    console.log(1);
    console.log(error)
})

//输出:
1
Error: 123
var promise = new Promise(function(resolve, reject){
    reject(new Error('123'))
}).then(function(data){
    console.log(data)
},function(error){
    console.log(1);
    console.log(error)
}).catch(function(err){
    console.log(2);
    console.log(err)
})

//输出:
1
Error: 123
var promise = new Promise(function(resolve, reject){
    reject(new Error('123'))
}).then(function(data){
    console.log(data)
}).catch(function(err){
    console.log(2);
    console.log(err)
})

//输出:
2
Error: 123

如果在then方法里面的第二次参数里面return new promise,那么会在catch里面捕获错误

var promise = new Promise(function(resolve, reject){
    reject(new Error('123'))
}).then(function(data){
    console.log(data)
},function(error){
    console.log(1);
    return new Promise(function(resolve, reject){reject(new Error('abc'))})
}).catch(function(err){
    console.log(2);
    console.log(err)
})
//输出:
1
2
Error: abc

catch方法并没有捕获到then方法内的new Error错误

var promise = new Promise(function(resolve, reject){
    resolve('123')
}).then(function(data){
    console.log(data)
    console.log(new Error('error'));
},function(error){
    console.log(1);
    
}).catch(function(err){
    console.log(2);
    console.log(err)
})

//输出
123
Error: error

并没有在catch方法里面输出error。

var promise = new Promise(function(resolve, reject){
    resolve('123')
}).then(function(data){
    console.log(data)
    return new Promise((resolve, reject)=>{new Error('error')});
},function(error){
    console.log(1);
    
}).catch(function(err){
    console.log(2);
    console.log(err)
})

//输出
123

只有promise里面调用resolve、reject函数才会在then方法里面有输出

var promise = new Promise(function(resolve, reject){
    resolve('123')
}).then(function(data){
    console.log(data)
    return new Promise((resolve, reject)=>{resolve(new Error('error'))});
},function(error){
    console.log(1);
    
}).then(function(data){console.log(data)})
.catch(function(err){
    console.log(2);
    console.log(err)
})

//输出:
123
Error: error
var promise = new Promise(function(resolve, reject){
    resolve('123')
}).then(function(data){
    console.log(data)
    return new Promise((resolve, reject)=>{reject(new Error('error'))});
},function(error){
    console.log(1);
    
}).then(function(data){console.log(data)})
.catch(function(err){
    console.log(2);
    console.log(err)
})

//输出:
123
2
Error: error
const p1 = new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('fail')), 3000)
})
const p2 = new Promise(function (resolve, reject) {
    setTimeout(() => resolve(p1), 1000)
})

p2.then(result => console.log(result))
    .catch(error => console.log(error))

//输出
Error: fail
var p1 = new Promise(function (resolve, reject) {
    setTimeout(() => reject(new Error('fail')), 0)
})
var p2 = new Promise(function (resolve, reject) {
    setTimeout(() => resolve(p1), 1000)
})

p2.then(result => console.log(result))
    .catch(error => console.log(error))

//输出
Promise {<pending>}
Error: fail

resolved的Promise是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。

new Promise((resolve, reject) => {
  resolve(1);
  console.log(2);
}).then(r => {
  console.log(r);
});
//输出
2
1
new Promise(function (resolve, reject) {
    resolve('123')
}).then(function (data) {
    console.log(data)
}).then(function (data) {
    console.log(data)
})

//输出
123
undefined
new Promise(function (resolve, reject) {
    resolve('123')
}).then(function (data) {
    console.log(data)
    return new Promise(function (resolve, reject) {
        reject('234')
    })
}).then(function (data) {
    console.log(data)
}).catch(function (data) {
    console.log(data)
})

//输出
123
234

三、Promise的方法

1、Promise.prototype.then()

then方法返回的是一个新的Promise实例,注意,不是原来那个Promise实例。因此可以采用链式写法,即then方法后面在调用另一个then方法。

function getPromise(data) {
    return new Promise(function (resolve, reject) {
        resolve(data)
    })
}
getPromise({
    aaa: 'bbb'
}).then(function (param) {
    return param
}).then(function (param2) {
    console.log(param2.aaa)
})

//输出
bbb

上面的then方法,依次指定了两个回调函数,第一个回调函数完成以后,会将返回结果作为参数,传入第二个回调函数。

2、Promise.prototype.catch()

Promise.prototype.catch方法是 .then(null, rejection)或 .then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

getJSON('/posts.json').then(function(posts){
    // ...
}).catch(function(error){
    // 处理getJSON 和 前一个回调函数运行时发生的错误
    console.log('发生错误!', error)
})

getJSON方法返回一个 Promise 对象,如果该对象状态变为resolved,则会调用then方法指定的回调函数;如果异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数,处理这个错误。另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。

p.then((val) => console.log('fulfilled:', val))
  .catch((err) => console.log('rejected', err));

// 等同于
p.then((val) => console.log('fulfilled:', val))
  .then(null, (err) => console.log("rejected:", err));
const promise = new Promise(function (resolve, reject) {
    throw new Error('test');
});
promise.catch(function (error) {
    console.log(error);
});

//输出
Error: test

上图等价于下图中的方法

// 写法一
const promise = new Promise(function(resolve, reject) {
  try {
    throw new Error('test');
  } catch(e) {
    reject(e);
  }
});
promise.catch(function(error) {
  console.log(error);
});

// 写法二
const promise = new Promise(function(resolve, reject) {
  reject(new Error('test'));
});
promise.catch(function(error) {
  console.log(error);
});

reject方法的作用,等同于抛出错误。

Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止。也就是说,错误总是会被下一个catch语句捕获。

function getPromise(data) {
    return new Promise(function (resolve, reject) {
        resolve(data)
    })
}
getPromise('123').then(function (param) {
    return new Promise(function (resolve, reject) {
        reject(new Error('test1'))
    })
}).then(function (param2) {
    return new Promise(function (rsolve, reject) {
        reject(new Error('test2'))
    })
}).catch(function (data) {//处理前面三个Promise产生的错误
    console.log(data)
})

//输出
Error: test1

上面代码中,一共有三个Promise对象:一个由getPromise产生,两个由then产生,它们之中任何一个抛出的错误,都会被最后一个catch捕获。

一般来说,不要在then方法里面定义Reject状态的回调函数(即then的第二个参数),总是使用catch方法。建议总是使用catch方法,而不使用then方法的第二个参数。

// bad
promise
  .then(function(data) {
    // success
  }, function(err) {
    // error
  });

// good
promise
  .then(function(data) { //cb
    // success
  })
  .catch(function(err) {
    // error
  });

跟传统的try/catch代码块不同的是,如果没有使用catch方法指定错误处理回调函数,Promise对象抛出的错误不会传递到外层代码,即不会有任何反应。

const someAsyncThing = function () {
    return new Promise(function (resolve, reject) {
        // 下面一行会报错,因为x没有声明
        resolve(x + 2)
    })
}
someAsyncThing().then(function () {
    console.log('everything is great');
})
setTimeout(() => {
    console.log(123)
}, 2000)

//输出
Uncaught (in promise) ReferenceError: x is not defined
123

someAsyncThing函数产生的 Promise 对象,内部有语法错误。浏览器运行到这一行,会打印出错误提示ReferenceError: x is not defined,但是不会退出进程、终止脚本执行,2 秒之后还是会输出123。这就是说,Promise 内部的错误不会影响到 Promise 外部的代码,通俗的说法就是“Promise 会吃掉错误”。

const promise = new Promise(function (resolve, reject) {
  resolve('ok');
  setTimeout(function () { console.log(x+2) }, 0)
});
promise.then(function (value) { console.log(value) });

//输出
ok
Uncaught ReferenceError: x is not defined

上面代码中,Promise指定在下一轮“事件循环”再抛出错误,到那个时候,Promise的运行已经结束了,所以这个错误是在Promise函数体外抛出的,会冒泡到最外层,成了未捕获的错误。

一般总是建议,Promise 对象后面要跟catch方法,这样可以处理 Promise 内部发生的错误。catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法。

const someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行会报错,因为x没有声明
    resolve(x + 2);
  });
};

someAsyncThing()
.catch(function(error) {
  console.log('oh no', error);
})
.then(function() {
  console.log('carry on');
});

//输出
oh no ReferenceError: x is not defined
carry on

上面代码运行完catch方法指定的回调函数,会接着运行后面那个then方法指定的回调函数。如果没有报错,则会跳过catch方法。

例如:

Promise.resolve()
.catch(function(error) {
  console.log('oh no', error);
})
.then(function() {
  console.log('carry on');
});

//输出
carry on

模糊的地方!!!

var someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行会报错,因为x没有声明
    resolve(x + 2);
  });
};

someAsyncThing().then(function() {
    console.log('2222')
  return someOtherAsyncThing();
}).catch(function(error) {
  console.log('oh no', error);
  // 下面一行会报错,因为 y 没有声明
  y + 2;
}).then(function() {
  console.log('carry on');
});

//输出
oh no ReferenceError: x is not defined
Uncaught (in promise) ReferenceError: y is not defined

上面代码中,catch方法抛出一个错误,因为后面没有别的catch方法了,导致这个错误不会被捕获,也不会传递到外层。

var someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行会报错,因为x没有声明
    resolve(x + 2);
  });
};

someAsyncThing().then(function() {
  return someOtherAsyncThing();
}).catch(function(error) {
  console.log('oh no', error);
  // 下面一行会报错,因为y没有声明
  y + 2;
}).catch(function(error) {
  console.log('carry on', error);
});

//输出
oh no ReferenceError: x is not defined
carry on ReferenceError: y is not defined

3、Promise.prototype.finally()

finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。

server.listen(port)
    .then(function(){
        // ...
})
.finally(server.stop)

finally方法的回调函数不接受任何参数,这样没办法知道,前面的Promise状态到底是成功 fulfilled 还是 失败rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于Promise的执行结果

promise
.finally(() => {
  // 语句
});

// 等同于
promise
.then(
  result => {
    // 语句
    return result;
  },
  error => {
    // 语句
    throw error;
  }
);
Promise.prototype.finally = function (callback) {
  let P = this.constructor;
  return this.then(
    value  => P.resolve(callback()).then(() => value),
    reason => P.resolve(callback()).then(() => { throw reason })
  );
};
// resolve 的值是 undefined
Promise.resolve(2).then(() => {}, () => {})

// resolve 的值是 2
Promise.resolve(2).finally(() => {})

// reject 的值是 undefined
Promise.reject(3).then(() => {}, () => {})

// reject 的值是 3
Promise.reject(3).finally(() => {})

4、Promise.all()

Promise.all()方法用于将多个Promise实例,包装成一个新的Promise实例。

const p = Promise.all([p1, p2, p3])

Promise.all()方法接收一个数组作为参数,p1、p2、p3都是Promise实例。如果不是Promise实例,就会调用Promise.resolve方法,将参数转为Promise实例,再进一步处理。另外,Promise.all()方法的参数可以不是数组,但必须具有Iterator接口,且返回的每个成员都是Promise实例。

p的状态由p1p2p3决定,分成两种情况。

(1)只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

注意:如果作为参数的Promise实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all() 的 catch方法

const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result)
.catch(e => e);

const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
})
.then(result => result)
.catch(e => e);

Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));

//输出
["hello", Error: 报错了]

p1resolvedp2首先会rejected,但是p2有自己的catch方法,该方法返回的是一个新的 Promise 实例,p2指向的实际上是这个实例。该实例执行完catch方法后,也会变成resolved,导致Promise.all()方法参数里面的两个实例都会resolved,因此会调用then方法指定的回调函数,而不会调用catch方法指定的回调函数。

如果p2没有自己的catch方法,就会调用Promise.all()catch方法。

const p1 = new Promise((resolve, reject) => {
  resolve('hello');
})
.then(result => result);

const p2 = new Promise((resolve, reject) => {
  throw new Error('报错了');
})
.then(result => result);

Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
//输出
Error: 报错了

5、Promise.race()

Promise.race()方法是将多个Promise实例,包装成一个新的Promise实例

const p = Promise.race([p1, p2, p3])

上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的回调函数。

栗子:在指定时间内没有获得结果,就将Promise的状态变为reject,否则变为resolve

var p = Promise.race([
    new Promise(function (resolve, reject) {
        setTimeout(() => resolve(123), 6000)
    }),
    new Promise(function (resolve, reject) {
        setTimeout(() => reject(new Error('request timeout')), 5000)
    })
]);

p
    .then(console.log)
    .catch(console.error);

//输出
Error: request timeout
var p = Promise.race([
    new Promise(function (resolve, reject) {
        setTimeout(() => resolve(123), 2000)
    }),
    new Promise(function (resolve, reject) {
        setTimeout(() => reject(new Error('request timeout')), 5000)
    })
]);

p
    .then(console.log)
    .catch(console.error);

// 输出
123

6、Promise.resolve()

将现有对象转为Promise对象。参数分为4种情况:

1)参数是一个Promise实例

2)参数是一个thenable对象

3)参数不是具有then方法的对象,或根本不是对象

4)不带有任何参数

7、Promise.reject()

Promise.reject(reason)方法也会返回一个新的Promise实例,该实例的状态为rejected。

四、应用

加载图片,可以将图片的加载写成一个Promise,一旦加载完成,Promise的状态就会发生变化。

const preloadImage = function (path) {
    return new Promise(function (resolve, reject) {
        const image = new Image();
        image.onload = resolve;
        image.onerror = reject;
        image.src = path;
    })
}

Generator函数与Promise的结合

使用Generator函数管理流程,遇到异步操作时,通常返回一个Promise对象。

function getFoo () {
  return new Promise(function (resolve, reject){
    resolve('foo');
  });
}

const g = function* () {
  try {
    const foo = yield getFoo();
    console.log(foo);
  } catch (e) {
    console.log(e);
  }
};

function run (generator) {
  const it = generator();

  function go(result) {
    if (result.done) return result.value;

    return result.value.then(function (value) {
      return go(it.next(value));
    }, function (error) {
      return go(it.throw(error));
    });
  }

  go(it.next());
}

run(g);

//输出
undefined
foo

async()/await()

yuansheng js

webassemble

原文地址:https://www.cnblogs.com/songya/p/11689646.html